How to watch nested Vuex store values - vue.js

I have a Vue project where I need to watch some Vuex store values. Here is the code:
computed: {
serverCategories: {
get() {
return this.$store.state[this.storeName].serverFilters.categories[0].value.name;
},
set(value) {
return value;
},
}
},
watch: {
serverCategories: {
deep: true,
handler(newVal, OldVal) {
console.log(newVal);
}
}
},
But it does not react on changes. serverCategories still contains the same value although the value in store state is changed. What is wrong with this code?

You can use the vuex mapGetters which you can wrap in your computed properties (see documentation here)
I would highly recommand using getters each time you need to access the store instead of $store.state.myStore.myValue.
You can also use vuex to mutate your data (through mutations and the vuex function mapMutations or though actions using mapActions)
Example :
store
getters: {
myValue: (state) => state.myValue
}
Vue component
<template>
<p> {{ myValueFromGetter }}</p>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
myValueFromGetter: 'myGetter/myValue'
}
}
</script>

Related

Confused about Vuex modules

I'll try and lay this out as cleanly as possible:
I have a Vue component that I'm rendering called World.vue:
<template>
<v-container>
This is the whole world
<v-btn #click="test()" />
</v-container>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState('drawers', ['isEquipmentOpen']),
},
methods: {
...mapActions('drawers', ['test']),
},
};
</script>
And I have a module in for the Vuex store called drawers (drawers.js):
export default {
namespaced: true,
state: {
isEquipmentOpen: false,
},
actions: {
test(state) {
console.log(state.isEquipmentOpen);
state.isEquipmentOpen = !state.isEquipmentOpen;
},
},
};
What I don't understand is why when test() is called, it can't access isEquipmentOpen (logs a value of undefined). How can I manipulate isEquipmentOpen?
You cannot mutate state values from actions.
You'll need to create a mutations property, create a setter method in it
and call it in order to change the value.
Pass a { commit } parameter to method in actions property.
You don't need to pass the state parameter in actions methods.
Make sure that the string value in the commit() is the same as the setter name.
export default {
namespaced: true,
state: {
isEquipmentOpen: false,
},
actions: {
test({ commit }) {
console.log(state.isEquipmentOpen);
commit("setIsEquipmentOpen");
},
mutations: {
setIsEquipmentOpen: state => state.isEquipmentOpen != state.isEquipmentOpen
},
};
Hope you understood the answer!

How to use a Nuxt component to pass a parameter to a vuex getter method? (while maintaining three dot notation)

GOAL: I would like to watch a vuex state object (MATCH_DATA) for changes, specific to the value of a prop (topicId). So, I would like to set the watcher to watch MATCH_DATA[topicId]. And whenever MATCH_DATA[topicId] updates, I'd like to call a function (in this early case just logging it).
However, since MATCH_DATA is a getter how would I pass a parameter to it? The vuex getter does not seem to be designed to take in parameters. I have seen some examples by explicitly calling this.$store.state.etc.etc. but is there a way to do this while retaining the three dot notation I currently have?
Or, is there some better way of achieving this goal that does not involve a vuex getter?
Current Code:
Nuxt component:
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
props: ['topicId'],
computed: {
...mapGetters('sessionStorage', ['MATCH_DATA']),
},
watch: {
MATCH_DATA (newMatchData, oldMatchData) {
console.log(newMatchData);
}
},
mounted() {
this.SUBSCRIBE_TO_TOPIC(this.topicId);
},
methods: {
...mapActions('sessionStorage', ['SUBSCRIBE_TO_TOPIC'])
}
}
vuex store:
/* State */
export const state = () => ({
MATCHES: {},
});
/* GETTERS */
export const getters = {
MATCH_DATA: (state) => {
return state.MATCHES;
},
};
Your getter can simply return a function like this:
export const getters = {
MATCH_DATA: (state) => {
return topicId => {
// return something
}}
},
};
Then you can create a computed property to access those getter:
export default {
computed: {
...mapGetters('sessionStorage', ['MATCH_DATA']),
yourComputedProperty () {
return this.MATCH_DATA(this.topicId)
}
}
}
Then you can watch the computed property to react on changes.
watch: {
yourComputedProperty (newData, oldData) {
console.log(newData);
}
}

Vue/Vuex: mapState inside computed is not updating

I am trying to make use of mapState and running into issues with reactive data. I have the following inside my Test.vue component
<template>
<div> {{ name }} </div>
</template>
computed: {
...mapState('user', ['age','name]
}
when my state user.name updates outside of the Test.vue component, the new value is not showing inside Test.vue.
so for example, if I have an update via a mutation in my userStore,
[SET_USER_NAME_MUTATION](state, value) {
state.name = value;
},
commit('SET_USER_NAME_MUTATION', "John")
now in my Vuex store when I check chrome DevTools , user { name: "John" } , which is correct
You should mutate state through vuex actions instead of directly calling the mutation.
Try with something like this, assuming your state contains a user object with name property:
Vue component
<template>
<div>
<span>{{ name }}</span>
<button #click="changeName">Change name</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'MyComponent',
computed: {
...mapState({
name: state => state.user.name
})
},
methods: {
changeName () {
this.$store.dispatch('changeName', 'John Smith')
}
}
}
</script>
Vuex store
// state
const state = {
user: {
name: null
}
}
// getters
const getters = {
// ...
}
// actions
const actions = {
changeName ({ commit }, payload) {
commit('setName', payload)
}
}
// mutations
const mutations = {
setName (state, payload) {
state.user.name = payload
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Anyway, it will be very helpful to know your state structure to a better approach as per your specific case

Vuex: How to update component data or call component methods from Store

If I have a store:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
foo: bar
},
mutations: {
updateComponent (state) {
// computation and logic
// update state
this.$refs.myComponent.updateComponent(state.foo)
}
}
}
And I have a component with ref 'myComponent':
<template>
...
</template>
<script>
export default {
...
methods: {
updateComponent(payload) {
// do stuff
}
}
}
</script>
I would like to call the 'updateComponent()' method from the store. I can use this.$refs.myComponent from other views and components, but it doesn't work from the Store. I get error TypeError: Cannot read property 'myComponent' of undefined.
Clearly this is not the correct scope for the this.$refs.myComponent when using from the Store.
Can I call updateComponent() from my store mutation, and how?
You could use vuex's subscribe method. Subscribe your component in it's mounted phase:
mounted() {
this.$store.subscribe((mutation, state) => {
switch(mutation.type) {
case 'updateComponent':
// Update your component with new state data
break;
}
})
}
Reference: https://vuex.vuejs.org/api/#subscribe

Why I commit to set the state's data to null, the component state do not change util I refresh the page?

In my store.js:
state: {
...
user_info: LocalStorageUtil.get('user_info')
},
mutations: {
set_user_info_null(state){
state.user_info = null
}
}
In my component:
// in the template
<div v-if="user_info" class="welcome">
<a #click="welcome_username">欢迎您,{{user_info.username}}!</a>/<a #click="logout">退出</a>
</div>
// in the script
data(){
return {
user_info: this.$store.state.user_info,
...
}
methods: {
...
... // in the logout method
LocalStorageUtil.clear('user_info')
this.$store.commit('set_user_info_null') // store set to null
You see, I clear the user_info first, and then Vuex to commit set_user_info_null. The Vuex's state user_info will be set to null.
but however, in the Component's data the user_info still is the Object:
EDIT -1
If I use the code:
import { mapState } from "vuex";
export default {
computed: {
...mapState("user_info")
}
};
I got a error:
TypeError: undefined is not an object (evaluating 'Object.keys(map)')
in this line:
computed: {
...mapState("user_info")
},
components: { // in this line
...
This part is problematic in your component
data(){
return {
user_info: this.$store.state.user_info,
...
}
You are declaring a local component state that is separated from the Vuex store. When you update Vuex store, this won't change.
The right way is by using mapState
import { mapState } from "vuex";
export default {
computed: {
...mapState(["user_info"])
}
};
It would be like you are passing user_info as a prop to this component. When Vuex store is updated, this component will be updated as well.
In the newer( higher than v.2.3.4 ) version Vue.js the code will check the set, if in your code has this.user_info=xxx :
You can check the source code.
So, first I will recommend you change the this.$store.state.user_info=xxx replace of this.user_info=xxx.
Second you should take a look at the Two-way Computed Property
computed: {
message: {
get () {
return this.$store.state.user_info
},
set (value) {
this.$store.commit('set_user_info_null')
}
}
}