2

I get this error in my console when I try to change the text of an input text of my components:

[vuex] do not mutate vuex store state outside mutation handlers

I also use nuxt with vuex.

I have this in my store:

editor.js

export const state = () => ({
  project_blocks: [{
    id: null,
    type: 'text-right',
    content:{
      title: 'Title 2',
      text: 'Text 2',
    }
  },{
      id: null,
      type: 'text-left',
      content:{
        title: 'Title 1',
        text: 'Text 1',
      }
    }],
});

export const mutations = {
  SET_BLOCKS(state, blocks){
    state.project_blocks = blocks;
  },
};


export const getters = {
  project_blocks(state){
    return state.project_blocks;
  }
};

In my component I have:

Index.vue

<template>
   <component v-for="(block, index) in project_blocks" :key="'module'+index" :block="block" :is="block.type"></component>
</template>

<script>
   export default{
      computed: {
         project_blocks:{
           get(){
             return this.$store.getters['editor/project_blocks'];
           },
           set(val){
             this.$store.commit('editor/SET_BLOCKS', val);
           }
         },
       }
   }
</script>

And in my "component type" I have:

TextLeft.vue

<template>
   <input type="text" v-model="block.content.title">
   <input type="text" v-model="block.content.text">
</template>

<script>
   export default{
      props:['block']
   }
</script>

When I try to change the text of these input text I get this error: [vuex] do not mutate vuex store state outside mutation handlers and I don't know how to fix it :(

Thanks people!

Marc Taulé
  • 115
  • 4

4 Answers4

1

The error message is being (surprisingly?) helpful here. When you v-model something, you're saying "when this input changes, modify the value of this thing I'm giving you."

However, as the error says, this is causing you to directly mutate data meant to be stored by Vuex, which isn't how Vuex likes things done. You should be using your mutations.

Edit: the following comment is wrong. Thanks, tao! Side note: Your set(val) function shouldn't be in computed, it should be in methods.

Lanny Bose
  • 1,811
  • 1
  • 11
  • 16
  • Using a [computed-setter](https://vuejs.org/v2/guide/computed.html#Computed-Setter) is perfectly valid. Computed are getter only by default but they can be getter/setter when you need them to (i.e: when used as `v-model` in an input). – tao May 14 '20 at 19:44
  • 1
    Ahh, good call. I'm not sure-Vuex-fluent so I didn't know that pattern. Thanks for the correction! – Lanny Bose May 14 '20 at 19:48
1

Vuex complains about v-model changing the state directly, because you're passing a getter (observable) to it.

To correctly update the state, you should use an action. Add an action performing that mutation and dispatch it inside the setter of your computed:

computed: {
  project_blocks:{
    get(){
      return this.$store.getters['editor/project_blocks'];
    },
    set(val){
      this.$store.dispatch('editor/setBlocks', val);
    }
  }
}

store/editor module:

actions: {
  setBlocks({ commit }, payload) {
    commit('SET_BLOCKS', payload);
  }
}

The mutation remains unchanged.


The example provided in forms only works because they're using a sub-property of obj. If they used obj itself (the top level getter value), they'd get the same error as you do.

tao
  • 82,996
  • 16
  • 114
  • 150
  • I also tried this option, but it appeared the same warning – Marc Taulé May 15 '20 at 07:38
  • What I posted here should work. You should create a [mcve], which would allow us to test any potential solutions and figure out all aspects. I'll only be able to look further into this in about 10-12 hours, if you haven't found an answer by then. – tao May 15 '20 at 08:42
0

Main point of actions is async calls. You can use Vuex mutations, but not change state directly. This is right immutability definition (You can check Vuex doc). I think the main problem now is that your computed property returns vuex getters result and there are vuex watchers on items. And when you are changing it - error is shown.

Asimple
  • 650
  • 3
  • 8
  • According to [docs](https://vuex.vuejs.org/guide/actions.html), the first difference between mutations and actions is that *"Instead of mutating the state, actions commit mutations."* and second is that they can contain async operations. – tao May 14 '20 at 20:16
  • But it dont tells you to use mutations only in actions. It tells you that you should change state only through Vuex mutations in the actions. Main immutable rule is that you can't change state directly. Vuex mutations is sync and actions is async. And if you don't need async, better way to use mutations. – Asimple May 14 '20 at 20:23
  • You can't use observables with `v-model`. You need to call a `next()` on the observable, which is what the action does. The advantage of getter/setter is that you don't need two names in the component and you also don't need to specify the method in `@change`. I believe it's much cleaner. – tao May 14 '20 at 20:24
  • 1
    You can just read [link](https://vuex.vuejs.org/guide/mutations.html) mutations chapter. At the bottom of the page they have described actions/mutations difference and why actions was added. – Asimple May 14 '20 at 20:27
  • Getter/setter has advantages. But here author should use it on lower level. – Asimple May 14 '20 at 20:34
0

I fix the error! I create a data value inside my component and I change this value individually with two mutations.

TextLeft.vue:

<template>
   <input type="text" v-model="title">
   <input type="text" v-model="text">
</template>

<script>
   export default{
      props:['block'],
      data(){
         title: this.block.content.title,
         text: this.block.content.text
      },
      watch:{
          title(val){
             this.$store.commit('editor/SET_TITLE', { index: this.block.index, 
title: val });
      },
          text(val){
            this.$store.commit('editor/SET_TEXT', { index: this.block.index, text: val });
      }
    }
   }
</script>
Marc Taulé
  • 115
  • 4