What is the correct way to deep clone / unbind a object in Vuex - vuex

I'm constantly getting the "[vuex] do not mutate vuex store state outside mutation handlers." error on my application. When I use a getter inside a component, I solve it destructuring the getter, something like:
const user = {
...this.$store.getters["Users/getUser"]
};
But sometimes, when I need to use something of my store state, inside a Vuex action, the only wat to "unbind" the object is using JSON.parse(JSON.stringify(state.something)), because destructuring still returns Vuex mutation error, and using the JSON.stringify and JSON.parse is probably the wrong way:
setActiveUser({ commit, state }, payload) {
return new Promise((resolve, reject) => {
let newUser = JSON.parse(JSON.parse(state.user))
etc
});
}
What is the correct way to unbind my state?
Thank you.

Related

vue - changing localstate triggers vuex error

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
}
}

Returning axios promises from vuex action

I can't figure out, if it's a bad approach to have vuex actions returning axios promises like the example below. The reason why I do it, is because I need some values from the vuex state in the request. Can anyone tell me, if this is bad to do or totally fine? :)
/* Vuex action */
fetchSomething({ state }, id) {
return getSomething(
id,
state.basket.shipping,
state.basket.billing);
},
/* Method in vue component */
someMethod() {
this.$store.dispatch('fetchSomething')
.then(res => console.log(res));
}
I don't see the point of doing this. The point of a Vuex action is to do the asynchronous job of retrieving the data and then to mutate the Vuex store.
Once the store has been mutated, you can access it from your Vue component. As Vuex is part of the Vue ecosystem it follows the same reactivity principles as Vue. If you need to execute something once you have retrieved the data you can set a watcher on the variable you are retrieving or more commonly just use it as a computed property.

Vuex State - Array empty

My Problem is, that the state-variable "genreRankings" in "store.js" is never updating.
Can somebody tell me why?
I'm accessing the Store via my Component as follows:
saveMovie (item) {
this.$store.dispatch('addMovie', item).then(x => {
console.log(this.$store.state.savedMovies)
this.$store.commit('update_genreRankings', Util.getGenreRankings(this.$store.getters.savedMovies))
})
},
removeMovie (item) {
this.$store.dispatch('removeMovie', item).then(x => {
this.$store.commit('update_genreRankings', Util.getGenreRankings(this.$store.getters.savedMovies))
})
},
Here is store.js (https://gist.github.com/oaltena/ccc70c06c29a1d9af6aa3234aba79518) and Util.js (https://gist.github.com/oaltena/67b8431199e9a6d74681c04d9183e630).
When i access the "genreRankings" via VueDevTools the array is always empty.
Help, please! :-)
Try "replacing" the state with a new array :
state.savedMovies = state.savedMovies.concat(object)
As written in the Vuex documentation, the state of Vuex store follows the same rules as the state in the components : https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules
PS: it's pretty ugly to call mutations directly from the components, use mapActions to map your actions in your components, then call commit from the action. You'll make a more maintenable code.
Try replacing this:
update_genreRankings (state, object) {
state.genreRankings = object
}
with this:
update_genreRankings (state, object) {
Vue.set(state, 'genreRankings', object)
}
Reference: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

vuex - is it possible to directly change state, even if not recommended?

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.

Emit an event when a specific piece of state changes in vuex store

I have a Vuex store with the following state:
state: {
authed: false,
id: false
}
Inside a component I want to watch for changes to the authed state and send an AJAX call to the server. It needs to be done in various components.
I tried using store.watch(), but that fires when either id or authed changes. I also noticed, it's different from vm.$watch in that you can't specify a property. When i tried to do this:
store.watch('authed', function(newValue, oldValue){
//some code
});
I got this error:
[vuex] store.watch only accepts a function.
Any help is appreciated!
Just set a getter for the authed state in your component and watch that local getter:
watch: {
'authed': function () {
...
}
}
Or you can use ...
let suscribe = store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
// call suscribe() for unsuscribe
https://vuex.vuejs.org/api/#subscribe