Reputation: 57176
Why do I get this error:
Error [vuex] Do not mutate vuex store state outside mutation handlers.
What does it mean?
It happens when I try to type in the edit input file.
pages/todos/index.vue
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" :checked="todo.done" v-on:change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button class="destroy" v-on:click="remove(todo)">delete</button>
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
</li>
<li><input placeholder="What needs to be done?" autofocus v-model="todo" v-on:keyup.enter="add"></li>
</ul>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
data () {
return {
todo: '',
editedTodo: null
}
},
head () {
return {
title: this.$route.params.slug || 'all',
titleTemplate: 'Nuxt TodoMVC : %s todos'
}
},
fetch ({ store }) {
store.commit('todos/add', 'Hello World')
},
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
},
methods: {
add (e) {
var value = this.todo && this.todo.trim()
if (value) {
this.$store.commit('todos/add', value)
this.todo = ''
}
},
toggle (todo) {
this.$store.commit('todos/toggle', todo)
},
remove (todo) {
this.$store.commit('todos/remove', todo)
},
doneEdit (todo) {
this.editedTodo = null
todo.text = todo.text.trim()
if (!todo.text) {
this.$store.commit('todos/remove', todo)
}
},
cancelEdit (todo) {
this.editedTodo = null
todo.text = this.beforeEditCache
},
},
directives: {
'todo-focus' (el, binding) {
if (binding.value) {
el.focus()
}
}
},
}
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
stores/todos.js
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text: text,
done: false
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
Any ideas how I can fix this?
Upvotes: 47
Views: 126194
Reputation: 1
I had a similar problem, a property in the data from the Vuex store needed to be manipulated for display purposes only. I got around the error with the following:
data() {
return {
myList: []
}
},
computed: {
...mapGetters(['storedList'])
},
async mounted() {
this.myList = this.storedList.map(item => {
return {...item, created: moment(item.created).format("MMMM Do YYYY")}
})
},
Upvotes: 0
Reputation: 46604
This error may come from the fact you shallow cloned an object.
Meaning that you've tried to copy an object but an object is not a primitive type (like String
or Number
), hence it's passed by reference and not value.
Here you think that you cloned one object into the other, while you are still referencing the older one. Since you're mutating the older one, you got this nice warning.
Here is a GIF from Vue3's documentation (still relevant in our case).
On the left, it's showing an object (mug) being not properly cloned >> passed by reference.
On the right, it's properly cloned >> passed by value. Mutating this one does not mutate the original
The proper way to manage this error is to use lodash
, this is how to load it efficiently in Nuxt:
lodash-es
, eg: yarn add lodash-es
, this is an optimized tree-shakable lodash ES modulenuxt.config.js
with the followingbuild: {
transpile: ['lodash-es'],
}
.vue
components like this<script>
import { cloneDeep } from 'lodash-es'
...
const properlyClonedObject = cloneDeep(myDeeplyNestedObject)
...
</script>
Few keys points:
JSON.parse(JSON.stringify(object))
because it does handle some edge-casesstructuredClone
is also native and quite performant if you're looking for a solution for a deep copy, bypassing the need for Lodash at all.Upvotes: 15
Reputation: 1
It is not your case but if someone is using typescript and is having the same problem, adding this: any
as the first param in your method or somewhere else should fix the problem
Upvotes: -2
Reputation: 184
I had to add mutation and call it instead of setting directly.
wrong:
someAction({state, rootState}) {
state.someValue = true;
}
right:
mutations: {
...
setSomeValue(state, val) {
state.someValue = val;
},
...
}
...
someAction({state, commit, rootState}) {
commit('setSomeValue', true);
}
Upvotes: 0
Reputation: 7364
If your data is an array with objects inside. Below snippet is the solution
const toyData = await this.$store.dispatch(
`user/fetchCoinToys`,
payload
)
const msgList = toyData.msglist.map((data) => {
return { ...data }
})
Upvotes: 0
Reputation: 5898
If you are using Vuex Modules, you might bump into this error if your module's data
property is an object, instead of a function that returns an object, and you are sharing this Module between more than one Store.
So instead of:
// In stores/YourModule.js
export default {
state: { name: 'Foo' },
}
Change it to:
// In stores/YourModule.js
export default {
state: () => {
return { name: 'Foo' };
},
}
This is actually documented here:
Sometimes we may need to create multiple instances of a module, for example:
Creating multiple stores that use the same module (e.g. To avoid stateful singletons in the SSR (opens new window)when the runInNewContext option is false or 'once'); Register the same module multiple times in the same store. If we use a plain object to declare the state of the module, then that state object will be shared by reference and cause cross store/module state pollution when it's mutated.
This is actually the exact same problem with data inside Vue components. So the solution is also the same - use a function for declaring module state (supported in 2.3.0+):
Upvotes: 1
Reputation: 29
export default new Vuex.Store({
...
strict: true
})
try to comment "strict"
Upvotes: 2
Reputation: 81
Just in case someone's still being troubled by this, I got my code working by making a duplicate/clone of the store state.
In your case, try something like this...
computed: {
todos () {
return [ ...this.$store.state.todos.list ]
}
}
It's basically a spread operator which results in making a clone of the todos.list array. With that, you're not directly changing the values of your state, just don't forget commit so your mutations will be saved in the store.
Upvotes: 5
Reputation: 455
Hello I have get the same problem and solve it with clone my object using one of the following:
{ ...obj} //spread syntax
Object.assign({}, obj)
JSON.parse(JSON.stringify(obj))
For your code I think you need to replace this part
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
}
With this
computed: {
todos () {
// console.log(this)
return {...this.$store.state.todos.list}
}
}
I don't make sure if this is the best way but hope this helpful for other people that have the same issue.
Upvotes: 30
Reputation: 14998
It could be a bit tricky to use v-model on a piece of state that belongs to Vuex.
and you have used v-model
on todo.text
here:
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
use :value
to read value and v-on:input
or v-on:change
to execute a method that perform the mutation inside an explicit Vuex mutation handler
This issue is handled here: https://vuex.vuejs.org/en/forms.html
Upvotes: 45
Reputation: 6544
There is no headache if you can use lodash
computed: {
...mapState({
todo: (state) => _.cloneDeep(state.todo)
})
}
Upvotes: 8