In one of my store modules (modal.js) I have a dialog state variable and a setDialogState mutation that assigns a value to dialog.
When calling setDialogState from my component, dialog value doesn't change. I know it because the value of this.$store.state.modal.dialog hasn't changed and also Vue Devtools extension shows the same old value for dialog.
What's going on?
In my store module store/modules/modal.js:
const state = {
dialog: false
}
const mutations = {
setDialogState (state, payload) {
this.state.dialog = payload;
console.log(this.state.dialog); // strangely, this correctly logs the new value
}
}
export default {
namespaced: true,
state,
mutations
}
And in my component:
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
dialog: state => state.modal.dialog
})
},
mounted() {
console.log(this.$store.state.modal.dialog);
this.$store.commit('modal/setDialogState', true);
setTimeout(()=> {
console.log(this.$store.state.modal.dialog);
}, 2000); // this should log the new value, yet it still logs the old value
}
}
</script>
You are trying to access to state by reference of this, there u can't apply mutation of your state.
The mutation mechanism is made to get the state in every function, then just have to use it as local variable inside of the current function.
Related
I have an array. I take data for it via rest api. I can call a mutation getData() from any component, but I need it to be automatically called when an object Vuex.Store is created, can I do this?
export default new Vuex.Store({
state: {
myArray: [],
},
mutations: {
getData() {
//get data from remote API and pass to myArray
axios.post('').then(response => {
this.myArray = response.data;
};
}
}
})
First things first: Mutations are synchronous pure functions. This means that your mutations should not have side-effects, and at the end of your mutation the state of your store should be updated to the new state. Axios uses promises and is thus asynchronous. You should do this in an action!
As for automatically executing a data fetch, you can either do this in the file where you define your store, or in your Vue entry point (e.g. App.vue) in a lifecycle hook. Keep in mind though that your axios call is asynchronous, which means that your app will load while data is loading in the background. You have to handle this case somehow.
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
const store = new Vuex.Store({
state: {
myArray: [],
},
mutations: {
setMyArray(state, payload) {
Vue.set(state, 'myArray', payload);
},
},
actions: {
fetchData({ commit }) {
axios.post('').then(response => {
commit('setMyArray', response.data);
};
}
}
});
// Setup
Vue.use(Vuex);
// Now that we have a store, we can literally just call actions like we normally would within Vue
store.dispatch('fetchData');
// Keep in mind that action is not blocking execution. Execution will continue while data is fetching in the background
I'm trying to figure out how to properly update a getter value when some other variable from VueX changes/updates.
Currently I'm using this way in a component to update:
watch: {
dates () {
this.$set(this.linedata[0].chartOptions.xAxis,"categories",this.dates)
}
}
So my getter linedata should be updated with dates value whenever dates changes. dates is state variable from VueX store.
The thing is with this method the value won't be properly updated when I changed route/go to different components. So I think it's better to do this kind of thing using the VueX store.
dates is updated with an API call, so I use an action to update it.
So the question is how can I do such an update from the VueX store?
EDIT:
I tried moving this to VueX:
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit("SET_DATA", {
this.linedata[0].chartOptions.xAxis,"categories": response.data.dates1,
this.linedata[1].chartOptions.xAxis,"categories": response.data.dates2
});
}
SET_DATA(state, payload) {
state = Object.assign(state, payload);
}
But the above does not work, as I cannot set nested object in action this way...
Getters are generally for getting, not setting. They are like computed for Vuex, which return calculated data. They update automatically when reactive contents change. So it's probably best to rethink the design so that only state needs to be updated. Either way, Vuex should be updated only with actions/mutations
Given your example and the info from all your comments, using linedata as state, your action and mutation would look something like this:
actions: {
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit('SET_DATA', response.data.dates);
}
}
mutations: {
SET_DATA(state, dates) {
Vue.set(state.linedata[0].chartOptions.xAxis, 'categories', dates[0]);
Vue.set(state.linedata[1].chartOptions.xAxis, 'categories', dates[1]);
}
}
Which you could call, in the component for example, like:
this.$store.dispatch('loadData');
Using Vue.set is necessary for change detection in this case and requires the following import:
import Vue from 'vue';
Theoretically, there should be a better way to design your backend API so that you can just set state.linedata = payload in the mutation, but this will work with what you have.
Here is a simple example of a Vuex store for an user.
export const state = () => ({
user: {}
})
export const mutations = {
set(state, user) {
state.user = user
},
unset(state) {
state.user = {}
},
patch(state, user) {
state.user = Object.assign({}, state.user, user)
}
}
export const actions = {
async set({ commit }) {
// TODO: Get user...
commit('set', user)
},
unset({ commit }) {
commit('unset')
},
patch({ commit }, user) {
commit('patch', user)
}
}
export const getters = {
get(state) {
return state.user
}
}
If you want to set the user data, you can call await this.$store.dispatch('user/set') in any Vue instance. For patching the data you could call this.$store.dispatch('user/patch', newUserData).
The getter is then reactively updated in any Vue instance where it is mapped. You should use the function mapGetters from Vuex in the computed properties. Here is an example.
...
computed: {
...mapGetters({
user: 'user/get'
})
}
...
The three dots ... before the function call is destructuring assignment, which will map all the properties that will the function return in an object to computed properties. Those will then be reactively updated whenever you call dispatch on the user store.
Take a look at Vuex documentation for a more in depth explanation.
I have a variable in the vuex store called permissions. And i want my component to trigger a rerender when the getPermissions changes. In the vue devtools i clearly see that the state has changed in the store, but the component stil get the old state from getPermissions. In order for me to see changes, I have to do a refresh. Has it something to do with the way i mutate it? or the fact that it is an object?
It looks like this when populated:
permissions: {
KS1KD933KD: true,
KD9L22F732: false
}
I use this method to do mutations on it and a getter to get it:
const getters = {
getPermissions: state => state.permissions
};
const mutations = {
set_recording_permissions(state, data) {
let newList = state.permissions;
newList[data.key] = data.bool;
Vue.set(state, 'permissions', newList);
}
};
And in the component i use mapGetters to get access to it
computed: {
...mapGetters('agentInfo',['getPermissions'])
}
In order to update the permissions value i use this action (it does require a succesfull api request before updating the value) :
const actions = {
async setRecordingPermissions({ commit }, data) {
let body = {
agentId: data.userName,
callId: data.callId,
allowUseOfRecording: data.allowUseOfRecording
};
try {
await AgentInfoAPI.editRecordingPermissions(body).then(() => {
commit('set_recording_permissions', { key: data.callId, bool: data.allowUseOfRecording });
commit('set_agent_info_message', {
type: 'success',
text: `Endret opptaksrettigheter`
});
});
} catch (error) {
console.log(error);
commit('set_agent_info_message', {
type: 'error',
text: `Request to ${error.response.data.path} failed with ${error.response.status} ${error.response.data.message}`
});
}
}
}
Since the getter only returns state variable you should use mapState, if you want to access it directly.
computed: mapState(['permissions'])
However, you can also use mapGetters, but then in your template, have to use getPermissions and not permissions.
Example template:
<ul id="permissions">
<li v-for="permission in getPermissions">
{{ permission }}
</li>
</ul>
If you have done this it is probably an issue with the object reference. You use Vue.set, but you set the same object reference. You have to create a new object or set the key you want to update directly.
new object
let newList = { ...state.permissions };
Vue.set
Vue.set(state.permission, data.key, data.value);
I don't know what the rest of you code looks like, but you will need to use actions to correctly mutate you store.
For example:
const actions = {
setName({ commit }, name) {
commit('setName', name);
},
}
I’m creating a custom module that fetches data in the “afterRegistration” hook and saves the result into the store.
The state is updated (can see the update in VueDevTools), but the component has still the default state. What I’m doing wrong?
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
store.dispatch(${KEY}/isVisible)
}
// component
export default {
name: 'Test',
components: { Fragment },
props: {
…
},
computed: {
isVisible: el => el.$store.getters['myModule/simpleTest']
}
}
You have lost the reactivity. Set the initial value of simpleTest in state object to whatever you like (from the context i see it's Boolean). There is no getter or setter for this field, if it's not in initial state.
Ok, I found out that I need to dispatch in an AsyncLoader
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
AsyncDataLoader.push({
execute: ({ route, store, context }) => {
return new Promise ((resolve, reject) => {
store.dispatch(`${KEY}/getInlineTranslationConfig`)
.then(() => resolve(null))
})
}
})
Now it works!
When I put this in my Vue component ...
// using store getter
computed: {
authenticated() {
return this.$store.getters.authenticated
}
}
... it works. The value for authenticated is reactive and the computed property returns true when the value in the vuex store is true.
This should work ... (and would be the right way according to the docs)
// using store state
computed: {
authenticated() {
return this.$store.state.authenticated
}
}
... but doesn't. The computed property is always false.
It doesn't even work on initial state, so I guess it has nothing to do with the action or mutation. The vuex store holds the correct values in the state and the getters (Firefox Vue DevTools).
My store looks like this:
const state = {
authenticated: authenticate.isAuthenticated(),
};
const getters = {
authenticated () {
return state.authenticated
}
};
const mutations = {
isAuthenticated (state, isAuthenticated) {
state.authenticated = isAuthenticated
}
};
So, it works with store getters but not with store state. Afaik the store state should be reactive as well.
Any idea what I might be doing wrong?
More as an aside to this discussion, vuex offers the mapGetters, mapState, mapActions, and mapMutations helper functions.
In the case of the authenticated getter, you would map it like:
import { mapGetters } from 'vuex
computed: {
...mapGetters({
authenticated: 'authenticated'
})
}
Helps to keep your code clean and concise, imo.
Assuming you construct your Vuex.Store as I do below, the computed works as expected using either state.authenticated or getters.authenticated.
The mutations section made no difference, so I took it out to make things minimal.
As Bert noted, your getter should take state as a parameter; otherwise, it is using the declared const, which is the same thing in this case, but deceptive to read.
const authenticate = {
isAuthenticated() {
return true;
}
};
const state = {
authenticated: authenticate.isAuthenticated()
};
const getters = {
authenticated (state) {
return state.authenticated;
}
};
const store = new Vuex.Store({
state,
getters
});
new Vue({
el: '#app',
store,
computed: {
authenticated() {
return this.$store.state.authenticated;
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<script src="//unpkg.com/vuex#latest/dist/vuex.js"></script>
<div id="app">
Anything? {{authenticated}}
</div>
const state = {
authenticated: authenticate.isAuthenticated(),
};
The state is an object. An attribute in the object is trying to call the result of a function. This might be the problem, as it would be asking an object to invoke functions. Try setting it to a fixed value first, and change the state value by invoking a mutation when needed.
You could also try js object function call to invoke the authenticate.isAuthenticated() function inside the state object.
Details here: https://www.w3schools.com/js/js_function_call.asp
Possible solution:
const state = {
authenticated: function(){ return authenticate.isAuthenticated() },
};
I do not think the problem is with using getters or state. Since state ran correctly, getters should do the same since it is pointing to state. Have you exported getters from your store? That seems to be the likely issue. As previously mentioned, you ought to pass state as a parameter when using vuex getters