Whenever i update local state, vuex throws error [vuex] do not mutate vuex store state outside mutation handlers. even though I am not even trying to change vuex store. What is the problem?
data: {
selected: []
},
methods: {
addItem(item){
this.selected.push({
name: item.name,
count: item.count
})
},
applySelected(){
this.$store.dispatch('changeItems', this.selected)
}
}
<button #click="item.count++"/>
<span>{{item.count}}</span>
<button #click="item.count--"/>
items are in loop but proper markup and surrounding code is unnecessary for that example.
Now when selected[] is empty, when i do addItem(item) it works fine. Then when i change count, it still works just fine. When i commit changed to the store, guess what - it still works fine. But when i try to change count after it was submitted to the store, even though i have 0 getters, not reading store at all, don't have any additional mutation/dispatch calls, whenever i try to change count of selected[] items, it throws vuex error. but why?
it doesnt make any sense to change vuex state in component. its better to change it via a mutation.
but there is a way to solve this locally and you have to call a function that returns new object of items in vuex store. maybe like this:
computed: {
selected() {
return () => this.$store.state.selected
}
}
Related
We are trying to detect whether a person is logged in or not using the vuex store state: loggedIn. When I call the API service from the action it calls the mutation after successful login and changes the data in the state:
loginSuccess(state, accessToken) {
state.accessToken = accessToken;
state.authenticating = false;
state.loggedIn = true;
console.log(state.loggedIn);
}
The console.log() shows the value, so the mutation is working.
In my other component, I use a computed property to watch for changes in the store using ...mapState() and bound the property in the template view:
computed: {
...mapState('authStore',['loggedIn' ]);
}
But the view never gets updated based on the computed property. I checked using the Vue dev tools in the console. It shows the state changes.
I have initialized the state.
export const states = {
loggedIn: false
};
I have tried to call the state directly.
this.$store.state.authStore.loggedIn;
I have tried different approaches.
...mapState('authStore', { logging:'loggedIn' });
//or
...mapState('authStore',['loggedIn' ]);
also, tried watch: {} hook but not working.
Interestingly though, the state's getter always shows undefined, but the state property changes in the dev tools.
Cannot figure out what is wrong or how to move further.
here is the screenshot of devtools state after successful login:
This catches my eye:
export const states = {
loggedIn: false
};
My suspicion is that you're then trying to use it something like this:
const store = {
states,
mutations,
actions,
getters
}
This won't work because it needs to be called state and not states. The result will be that loggedIn is unreactive and has an initial value of undefined. Any computed properties, including the store's getter, will not be refreshed when the value changes.
Whether my theory is right or not, I suggest adding console.log(state.loggedIn); to the beginning of loginSucess to confirm the state prior to the mutation.
I have a Vuex store that manages an array (state.all), and I have a button that calls a Vuex action which performs an HTTP call and then appends the the data in the response to state.all by way of a mutation. However, the state never gets updated and the components never update.
In order prove that I was not crazy, I used two alert()s inside of the mutation to make sure I knew where I stood in the code. The alert()s were always fired with proper values.
Here is the truncated Vuex store (this is a module):
const state = {
all: []
}
// actions
const actions = {
...
runner ({ commit, rootState }, { did, tn }) {
HTTP.post(url, payload)
.then(function (response) {
commit('setNewConversations', response.data)
})
})
}
}
const mutations = {
...
setNewConversations(state, new_conv) {
for (let new_c_i in new_conv) {
let new_c = new_conv[new_c_i]
alert(new_c) // I always see this, and it has the correct value
if (!(new_c in state.all)) {
alert('I ALWAYS SEE THIS!') // testing
state.all.push(new_c)
}
}
}
...
}
When I go to test this, I see my two alert()s, the first with the value I expect and the second with "I ALWAYS SEE THIS!" but nothing happens to my v-for component and the state never updates, despite the state.all.push().
What is the next step to troubleshooting this issue? There are no errors in the JS console, and I cannot figure out any reason the state would not be updated.
Thank you!
One possible solution is instead of pushing to the current state value, store the previous value of state.all in a new array and push the new changes to that new array.
Once done, assign that new array to state.all like the following below.
setNewConversations(state, new_conv) {
const prevState = [...state.all];
for (let new_c_i in new_conv) {
let new_c = new_conv[new_c_i]
if (!(new_c in prevState)) {
prevState.push(new_c);
}
}
state.all = prevState;
}
Given that you said that removing the alert makes it work makes me wonder if you are just observing the value in the wrong place. I can't be sure from what you've given.
Remember that Javascript is single-threaded, and your mutation has to complete before any other Vue-injected reactivity code can run.
If you really wanted the value to be shown before the mutation is complete, you could probably call Vue.nextTick(() => alert(...)), but the better answer is to check for the updates somewhere else, such as in a computed that calls the getter for the state.all array.
(By the way, I find that using either console.log(...) or the vue-dev-tools is much faster than alert() for arbitrary debugging.)
I'm trying to set token to my store.token I know this is not a best option without using mutation but I'm doing something like this:
methods : {
molestor(){
const self = this;
this.$store.state.token = "new token";
this.$store.state.cleavage= "yes";
this.$store.commit('settoken', "somethingrandom");
},
}
Then on my store.js:
export const store = new Vuex.Store({
state : {
token : '',
},
mutations : {
settoken(state,token){
console.log(token);
}
}
});
Right now it works fine... it sets up. But when I remove the mutation from store.js or remove the commit on my molester() it wont assign the value to token. Why is this happening?
To set the value of state in store, we have to interact with Vuex api via mutations/commits.
By trying to set the state without a mutation, this goes against the design of Vuex (having a manageable store/state)
Typically trying to set state without mutations (say within an action) will throw an error, but I also believe that by getting the state via ‘$store.state’ will only return the state (and not return the instance of state)
This is done to maintain immutability throughout your application state
If you're expecting to see the change appear in the Vue dev tools you won't see any changes to state unless they occur through a mutation or until another mutation is called.
I have a list of todos that I'd like to watch and auto update when a change is made in a firebase backend
Got a bit stuck on what i hope is the last step (I am using nuxtjs for a SPA)
I have a getter in my vuex store as follows
getMyState: state => {
return state.todos
}
I am returning this getter to my component as follows
computed: {
...mapGetters(['getMyState'])
},
How do i now get my list to recognise when something has changed and update my array of results?
Thanks
Use watch property
watch: {
getMyState: function (new, old) {
}
},
The documentation here
says,
You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations.
My question is, is that good practice, or is that how the internals of the Vuex state works? In other words, is the Vuex state reactive in the same way Vue data is (it converts the JS object to an observable), or is it something else?
A similar question - could you directly change the state in an action instead of creating a mutation? I know it's bad practice and it loses some of the traceability that following the conventions gives - but does it work?
Could you directly change the state in an action instead of creating a mutation? I know it's bad practice and it loses some of the traceability that following the conventions gives - but does it work?
Works, but throws a warning AND an error.
vue.js:584 [Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] Do not mutate vuex store state outside mutation handlers."
(found in <Component>)
warn # vue.js:584
...
vue.js:1719 Error: [vuex] Do not mutate vuex store state outside mutation handlers.
at assert (VM260 vuex.js:103)
who knows what else might be broken after this.
See for yourself (notice the data updates in the template):
const store = new Vuex.Store({
strict: true,
state: {
people: []
},
mutations: {
populate: function (state, data) {
//Vue.set(state, 'people', data);
}
}
});
new Vue({
store,
el: '#app',
mounted: function() {
let self = this;
this.$http.get('https://api.myjson.com/bins/g07qh').then(function (response) {
// setting without commit
Vue.set(self.$store.state, 'people', response.data);
//self.$store.commit('populate', response.data)
}).catch(function (error) {
console.dir(error);
});
},
computed: {
datadata: function() {
return this.$store.state.people
}
},
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/vue-resource"></script>
<div id="app">
Data: {{ datadata }}
</div>
the Vuex state reactive in the same way Vue data is (it converts the js object to an observable), or is it something else?
Yes. Actually, that's Vue itself that makes the store objects reactive. From the Mutations official docs:
Mutations Follow Vue's Reactivity Rules
Since a Vuex store's state is made reactive by Vue, when we mutate the
state, Vue components observing the state will update automatically.
This also means Vuex mutations are subject to the same reactivity
caveats when working with plain Vue:
Prefer initializing your store's initial state with all desired fields upfront.
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one. For example, using the stage-3 object spread
syntax we can
write it like this:
state.obj = { ...state.obj, newProp: 123 }
So even within mutations code, if you overwrite observables or create new properties directly (by not calling Vue.set(obj, 'newProp', newValue)), the object won't be reactive.
Follow up questions from comments (good ones!)
So it seems the observable object is slightly different than the regular Vue data - changes are only allowed to happen from a mutation handler. Is that right?
They could be, but I don't believe they are. The docs and evidences (see below vm.$watch discussion below) point torward they being exactly the same as data objects, at least with regards to reaction/observable behaviors.
How does the object "know" it was mutated from a different context?
This is a good question. Allow me to rephrase it:
If calling Vue.set(object, 'prop', data); from within Vue throws an exception (see demo above), why calling Vue.set(object, 'prop', data); from within a mutation function doesn't?
The answer lies within Store.commit()'s code. It executes the mutation code through a _withCommit() internal function.
All that this _withCommit() does is it sets a flag this._committing to true and then executes the mutation code (and returns _committing to false after the exection).
The Vuex store is then watching the states' variables and if it notices (aka the watcher triggers) that the variable changed while the _committing flag was false it throws the warning.
(Bonus: do notice that vuex uses vm.$watch --see Vue's vm.$watch API docs if you are not familiar with it -- to observe the variables, another hint that state's objects are the same as data objects - they rely on Vue's internals.)
Now, to prove my point, let's "trick" vuex by setting state._committing to true ourselves and then call Vue.set() from outside a mutator. As you can see below, no warning is triggered. Touché.
const store = new Vuex.Store({
strict: true,
state: {
people: []
},
mutations: {
populate: function (state, data) {
//Vue.set(state, 'people', data);
}
}
});
new Vue({
store,
el: '#app',
mounted: function() {
let self = this;
this.$http.get('https://api.myjson.com/bins/g07qh').then(function (response) {
// trick the store to think we're using commit()
self.$store._committing = true;
// setting without commit
Vue.set(self.$store.state, 'people', response.data);
// no warning! yay!
}).catch(function (error) {
console.dir(error);
});
},
computed: {
datadata: function() {
return this.$store.state.people
}
},
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/vue-resource"></script>
<div id="app">
Data: {{ datadata }}
</div>
I am going to make this very simple:
Because the state object is already reactive, you can completely avoid using getters and mutations. All of Vue’s templates, computed, watch, etc. will continue to work the same as if using a component’s data. The store’s state acts as a shared data object.
But by doing so you will lose the ability to implement time-travel debugging, undo/redo, and setting breakpoints, because you will have circumvented the command design pattern and encapsulation of a member by using methods.