Vue.js + Vuex: How to mutate nested item state?
Asked Answered
C

3

10

let's say I have following tree:

[
    {
        name: 'asd',
        is_whatever: true,
        children: [
            {
                name: 'asd',
                is_whatever: false,
                children: [],
            },
        ],
    },
],

The tree is stored in a module via Vuex under key 'tree' and looped through with following recursive component called 'recursive-item':

<li class="recursive-item" v-for="item in tree">
    {{ item.name }}

    <div v-if="item.is_whatever">on</div>
    <div v-else>off</div>

    <ul v-if="tree.children.length">
        <recursive-item :tree="item.children"></recursive-item>
    </ul>
</li>

Now i want to toggle item's property 'is_whatever', so i attach a listener

    <div v-if="item.is_whatever" 
         @click="item.is_whatever = !item.is_whatever">on</div>
    <div v-else>off</div>

When i click it, it works, but emits following

"Error: [vuex] Do not mutate vuex store state outside mutation handlers."
[vuex] Do not mutate vuex store state outside mutation handlers.

How am I supposed to implement it without this error? I can see no way how to dispatch an action or emit event to the top of the tree because it's nested and recursive, so I haven't got a path to the specific item, right?

Cutcherry answered 13/8, 2017 at 21:30 Comment(1)
Per LinusBorg (4/2017): "The best thing to do is to avoid nested structures. This is an established pattern in ... redux" Instead, normalize your dataRacketeer
C
5

After consulting with some other devs later that evening we came with few ways how to achieve it. Because the data are nested in a tree and I access the nodes in recursive manner, I need to either get the path to the specific node, so for example pass the index of a node as a property, then add the child index while repeating that in every node recursively, or pass just the id of a node and then run the recursive loop in the action in order to toggle its properties.

More optimal solution could be flattening the data structure, hence avoiding the need for a recursion. The node would be then accessible directly via an id.

Cutcherry answered 31/8, 2017 at 11:23 Comment(1)
Hi Beau.... While searching the same problem i faced.. I got your post. Will you please give some sample code or info how will you achieve that?? I have also same kind data structure but there are 3 depth level.... Please help me on thisWineskin
S
0

Right now you're changing the state object directly by calling item.is_whatever = !item.is_whatever, what you need to do is create a mutation function that will execute that operation for you to guarantee proper reactivity:

const store = new Vuex.Store({
  state: { /* Your state */ },
  mutations: {
    changeWhatever (state, item) {
      const itemInState = findItemInState(state, item); // You'll need to implement this function
      itemInState.is_whatever = !item.is_whatever
    }
  }
})

Then you need to expose this.$store.commit('changeWhatever', item) as an action in your view that'll be trigger by the click.

Stirps answered 13/8, 2017 at 23:52 Comment(1)
That's the reason of the warning emitted, but i was aware of that (see the last paragraph). The problem is that the data had a tree structure and I had no idea how to toggle the specific item via an action or a commit... So, you're right, but answering different question - I was interested in findItemInState(state, item) implementation or a way how to pass the item idCutcherry
M
0

There is a debatable solution, but I'll just leave it here.

State:

state: {
  nestedObject: {
    foo: {
      bar: 0
    }
  }
}

There is Vuex mutation:

mutateNestedObject(state, payload) {
  const { callback } = payload;
  callback(state.nestedObject);
},

And this is an example of use in a component:

this.$store.commit('mutateNestedObject', {
  callback: (nestedObject) => {
    nestedObject.foo.bar = 1;
  },
});
Mesmerism answered 22/11, 2021 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.