Vuex mapState based on route and route parameters - vue.js

I have a works component I use different pages on my app and I am trying to load the state based on the route and route parameters.
In my App.vue file I dispatch the async action to get the json file like
mounted() {
this.$store.dispatch('getData')
},
And I map the state in my works component like that
export default {
name: 'Works',
computed: mapState({
works: (state) => state.works.home.items.slice(0, state.works.home.loadedCount),
loadedCount: (state) => state.works.home.loadedCount,
totalCount: (state) => state.works.home.items.length,
})
}
I actually need to map the state dynamically based on the route just like state.works[this.$router.currentRoute.params.category] or based on route name.
Could you please tell me what is the correct way to get the data (async) from my state?
Vuex store:
export default new Vuex.Store({
state: {
works: {
all: {
items: [],
loadedCount: 0,
},
home: {
items: [],
loadedCount: 0,
},
web: {
items: [],
loadedCount: 0,
},
print: {
items: [],
loadedCount: 0,
},
},
limit: 2,
},
mutations: {
SET_WORKS(state, works) {
state.works.all.items = works
works.map((el) => {
if (typeof state.works[el.category] !== 'undefined') {
state.works[el.category].items.push(el)
}
})
},
},
actions: {
getData({ commit }) {
axios
.get('/works.json')
.then((response) => {
commit('SET_WORKS', response.data.works)
})
},
},
})

You can do it in beforeCreate hook.
beforeCreate(){
const category = this.$route.params.category;
Object.assign(this.$options.computed, {
...mapState({
categoryItems: (state) => state.categories[category],
}),
});
}
I've created a basic working example: https://codepen.io/bgtor/pen/OJbOxKo?editors=1111
UPDATE:
To get mapped properties updated with route change, you will have to force re-render the component. The best way to do it, is to change the component key when route change in parent component.
Parent.vue
<template>
<categoryComponent :key="key"></categoryComponent> // <-- This is the component you work with
</template>
computed: {
key(){
return this.$route.params.category
}
}
With this approach the beforeCreate hook will be triggered with every route change, getting fresh data from Vuex.

Related

trigger method in parent from child nested in router in Quasar (vue)

I got this structure with nested routes in my router using Quasar framework (vue 3):
const routes = [
{
path: "/",
component: () => import("layouts/myLayout.vue"),
children: [
{
path: "",
component: () => import("pages/Main.vue"),
children: [
{
path: "",
component: () => import("components/Sub.vue")
}
]
}
]
}
I know I can use $emit in my child to pass to parent like this:
MyChild:
this.$emit("myEvent", "hello world");
MyParent:
<MyChild #myEvent="updateMyEvent" />
However I want to trigger an event in parent without rendering MyChild once again in the parent since it is already displayed through the router ... So I am looking for a way of accessing the parents method more directly so to speak.
What is the correct implementation of doing this in vue 3?
UPDATE:
my method in child for updating vuex:
this.updateValue(JSON.parse(JSON.stringify(myValue)));
Store.js
vuex:
const state = {
value: 0
};
const actions = {
updateValue({ commit }, payload) {
commit("updateMyValue", payload);
},
const mutations = {
updateMyValue(state, payload) {
state.myValue = payload;
},
};
const getters = {
getValue: state => {
return state.value;
},
I actually ended up with a watcher on my getter in my parent:
computed: {
...mapGetters("store", ["getValue"]) // module and name of the getter
},
watch: {
getValue(val) {
console.log("MY VALUE: " , val);
}
In the child parent component define a computed property called stateValue that's based on the store state value and then watch it to trigger that event :
computed:{
stateValue(){
return this.$store.state.value;
}
},
watch:{
stateValue(val){
this.updateMyEvent()// trigger the method
}
}

Two way data flow on Vue component

I want a selector in a Vue component to update when the stored value in Vuex is updated. Here is a simplified version of the vue component:
<template>
<v-autocomplete
outlined
dense
v-model="team"
label="Select Team"
:items="teams"
item-text="name"
item-value="_id"
return-object
class="mx-3 mt-3"
#change="selectTeam"
></v-autocomplete>
</template>
The JS:
<script>
export default {
name: 'NavDrawer',
data() {
return {
team: null,
teams: [],
};
},
async created() {
this.team = this.$store.getters['teams/allTeams'].find(
(t) => t.name === this.$route.params.team,
);
this.teams = this.$store.getters['teams/allTeams'];
},
methods: {
async selectTeam() {
if (this.team) {
await this.$store.dispatch('editTeam/selectTeam', this.team);
this.$router.push(`/team/${this.team.name}`);
} else {
this.$router.push('/');
}
},
},
};
</script>
And the Vuex store:
export default {
namespaced: true,
state: () => ({
editedTeam: {},
}),
mutations: {
selectTeam(state, team) {
state.editedTeam = team;
},
resetTeam(state) {
state.editedTeam = {};
},
},
actions: {
selectTeam({ commit }, team) {
commit('selectTeam', team);
},
resetTeam({ commit }) {
commit('resetTeam');
},
},
getters: {
getSelectedTeam: (state) => state.editedTeam,
},
};
I'm not sure if it matters, but this vuex store is passed into an index file to create the Vuex.Store -- this is working correctly and is not the issue.
I want the team stored in editedTeam to reactively update the team selected in the v-autocomplete component, when a new editedTeam is selected elsewhere in the application. I want the v-autocomplete selector to send a new team to editedTeam when it is selected. I think I should be using mapState for this and storing them as a computed value -- but nothing I've tried has worked. I find the vuex documentation to be lacking in good examples.
Thanks!

Vuex state changes are not propagated to Vue component template

I just started working on Vue and Vuex. I have created a component with its state data in Vuex. After an action, I can see my state changes applied in mutation, however, my Vue component is still not able to pick the new changes up.
Here's my store file:
const state = {
roomInfo: {
gameID: null,
userID: null,
},
seats: null,
};
const getters = {
seats: state => state.seats,
roomInfo: state => state.roomInfo,
};
const actions = {
async streamSeats({ commit }) {
let connection = new WebSocket(`ws://localhost:8080/api/game/${state.roomInfo.gameID}/seats/${state.roomInfo.userID}`)
connection.onmessage = function(event) {
commit('setSeats', event.data);
}
connection.onopen = function() {
console.log("Successfully connected to the echo websocket server...")
}
connection.onerror = function(event) {
console.log("ERRR", event)
}
},
async setRoomInfo({ commit }, roomInfo) {
commit('setRoomInfo', roomInfo);
},
};
const mutations = {
setSeats: (state, seats) => {
state.seats = seats
// I can see changes here properly
console.log(seats);
},
setRoomInfo: (state, roomInfo) => {
state.roomInfo.gameID = roomInfo.gameID;
state.roomInfo.userID = roomInfo.userID;
if (roomInfo.seatNumber === 1) {
state.seats.p1.id = roomInfo.userID;
}
},
};
export default {
state,
getters,
actions,
mutations,
};
And this is my component:
<template>
{{ seats }}
</template>
<script>
/* import API from '../api' */
import { mapGetters, mapActions } from 'vuex';
export default {
name: "Seats",
methods: {
...mapActions([
'streamSeats',
'setRoomInfo',
]),
},
computed: {
...mapGetters([
'seats',
'roomInfo',
'setSeats',
]),
},
watch: {
roomInfo: {
handler(newValue) {
if (newValue.userID && newValue.gameID) {
this.streamSeats();
}
},
deep: true,
},
},
components: {},
data: function() {
return {
alignment: 'center',
justify: 'center',
}
},
created() {
let gameID = this.$route.params.id
this.setRoomInfo({
gameID: gameID,
userID: this.$route.params.userID,
seatNumber: 1,
});
},
}
</script>
As you can see, I'd like to change the state data for seats inside state, after it connects to websocket server.
I have spent a long time trying to figure this out with no luck. I've tried to use mapstate, data, and a few other tricks without any luck. I tried all the suggested solutions in similar stackoverflow threads as well. I'd really appreciate if someone could give me some hints on how to pass this obstacle.
There are some mismatch when you define getters and call mapGetters
store
const getters = {
seatsd: state => state.seats, // there is a typo in seats, you declared seatsd
roomInfo: state => state.roomInfo,
};
component
computed: {
...mapGetters([
'seats',
'roomInfo',
'setSeats', // this is not getters, this is mutations
]),
},
Thank you for looking at it. I installed Vuejs chrome extension today. Apparently it changed the way errors were displayed in chrome dev console. I just noticed I had a few uncaught errors elsewhere, which didn't allow the code to go through these parts properly. After resolving those issues, I was able to see the data in my component.

Wait for mapped store getter to update child property

I have the following vuex getters
import { isEmpty } from 'lodash'
// if has token, we assume that user is logged in our system
export const isLogged = ({ token }) => !isEmpty(token)
// get current user data
export const currentUser = ({ user }) => user
export const timeLimit = ({ token_ttl }) => token_ttl
export const getToken = ({ token }) => token
I have the following computed Vuex properties in a child component
name: "ProfilePic",
computed: {
...mapGetters(['currentUser']),
url() {
return new String('').concat(window.location.protocol, '//', window.location.hostname , ':8000', '/games/create/?search=player_id:').valueOf()
}
},
mounted(){
console.log(this.currentUser)
},
watch: {
currentUser(value, old){
console.re.log('ok', value, old);
new QRCode(document.querySelector(".profile-userpic"), {
text: this.url + value,
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
})
}
}
the parent
import ProfilePic from '../../components/general/qrcode.vue'
export default {
name: 'CcDashboard',
methods : {
...mapActions(['checkUserToken', 'setMessage'])
},
computed: {
...mapGetters(['isLogged'])
},
mounted() {
this.checkUserToken().then(tkn => this.$store.dispatch('setMessage', {type: 'success', message: 'Your Game Starts Now!!!!'})).catch(err => this.$store.dispatch('setMessage', {type: 'error', message: ['Your time is up!']}))
},
components: {
'profile-pic': ProfilePic
}
}
Store
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
modules,
plugins,
getters,
strict: false, //process.env.NODE_ENV !== 'production',
})
I'm using VuexPersist with localforage
localforage.config({
name: 'vuevue'
});
const vuexLocalStorage = new VuexPersist({
key: 'vuex', // The key to store the state on in the storage provider.
storage: localforage, // or window.sessionStorage or localForage
// Function that passes the state and returns the state with only the objects you want to store.
// reducer: state => ({ Collect: state.Collect, Auth: state.Auth}),
// Function that passes a mutation and lets you decide if it should update the state in localStorage.
// filter: mutation => (true)
modules: ['Auth','Collect'],
asyncStorage: true
})
export const RESTORE_MUTATION = vuexLocalStorage.RESTORE_MUTATION
// // create a new object and preserv original keys
export default [...app.plugins, vuexLocalStorage.plugin]
executing console.log on mounted() I get
{__ob__: Observer}current_points: 45email: "qhegmann#jast.com"id: 2name: "Sandrine Cruickshank"total_points: 45__ob__: Observerdep: Dep {id: 20, subs: Array(4)}value: {id: 2, name: "Sandrine Cruickshank", email: "qhegmann#jast.com", current_points: 45, total_points: 45, …}
However,
When running the logic the this.currentUser.id returns undefined rather than a value( which it does)
Is it that I need to "wait" for it to properly populate from the store? or do I need to call it from the $store.dispatch() ?
I guess what you want here is to watch the state of your computed property itemGetter, and when itemGetter is different from null/undefined trigger the method createProductSet ? https://v2.vuejs.org/v2/guide/computed.html
computed : {
...mapGetters([
'itemGetter'
])
},
watch : {
itemGetter(newVal, oldVal) {
if (typeof newVal == null || typeof newVal == undefined)
return
this.createProductSet(newVal)
}
},
methods : {
createProductSet(id){
// logic
}
}
Figured it out and had to do with a bug with one of my packagesenter link description here

Vuex filter state

I'm at my first app in Vuejs Vuex.
I can not find the best way to filter a state.
store/index.js
state: {
projects: []
},
mutations: {
SET_PROJECT_LIST: (state, { list }) => {
state.projects = list
}
},
actions: {
LOAD_PROJECT_LIST: function ({ commit }) {
axios.get('projects').then((response) => {
commit('SET_PROJECT_LIST', { list: response.data})
}, (err) => {
console.log(err)
})
}
}
in the component:
computed: {
...mapState({
projects
})
}
At this point I have a list of my projects. Good!
Now I added buttons to filter my projects like:
Active Project, Type Project ...
How do I manipulate my projects object (this.projects)?
With another one this.$store.dispatch
With another getters function
I manipulate the state without changing the status?
I'm a bit confused.
Some examples of filters on lists populated in Vuex?
EDIT:
I was trying that way:
this.$store.getters.activeProjects()
But how I update this.projects?
activeProjects(){
this.projects = this.$store.getters.activeProjects()
}
does not work
I'd recommend to keep your original state intact and filter its data by using "getters".
In fact, the official documentation includes an example of how to get all the "done" todos. It might be useful for you:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getters reference: https://vuex.vuejs.org/en/getters.html
Good luck!