[Vuejs]-Vuex `people[0] = { age: people[0].age + 1 };` won't trigger re-render, why?

1👍

From the docs

Vue cannot detect property addition or deletion.

and

…it’s possible to add reactive properties to a nested object using the Vue.set(object, propertyName, value) method

and

Vue cannot detect the following changes to an array:

  • When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
  • When you modify the length of the array, e.g. vm.items.length = newLength

But it says nothing about mutating an existing object[property].

  • In the first example, an existing object property is being mutated, so it works. (The age property of the first item of the people array.)

  • In the second example, an array item is being set directly by index, so it doesn’t work. (The 0 index of the people array.)

This can seem counter-intuitive because a previously set array index can’t be modified by index, but a previously set object property can.

I’m guessing that the reason for this has a lot to do with Object.defineProperty and Vue’s reactivity process.

(Confused me at first, too.)

👤Dan

0👍

It’s a caveat about Vue.js reactivity.
You should use Vue.set if you want to change link to object.
For example it will work:

changePeopleNotWorking(state) {
      const newPeople = [...state.people];
      newPeople[0].age = newPeople[0].age + 1;
      Vue.set(state, "people", newPeople);
}

https://codesandbox.io/s/dreamy-cdn-x90sk?file=/src/main.js

UPD:

From Docs:

Vue cannot detect the following changes to an array:

When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue

When you modify the length of the array, e.g. vm.items.length = newLength

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // is NOT reactive
vm.items.length = 2 // is NOT reactive

So, using an array you cannot change item by index (even existing item), because it’s not reactive.

But you can use

Vue.set(vm.items, indexOfItem, newValue)

For example:

Vue.set(state.people, 0, { age: state.people[0}.age + 1} )

UPD 2.
1 and 2 they are not the same. In the first example we don’t re-assign array item by link, instead this we change property of observable object. If you look at state in console.log you will see, that objects in array (which were initialized) are observable. So, Vue is able to watch changes there.

enter image description here

👤Georgy

0👍

Properties of plain objects are reactive (defined as getter/setter pair), while array indexes aren’t:

reactive array

This assignment of array element results in non-reactive plain object:

people[0] = { age: people[0].age + 1 }

This is the limitation of Vue reactivity for arrays:

Vue cannot detect the following changes to an array:

  • When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
  • When you modify the length of the array, e.g. vm.items.length = newLength

It can be addressed with Vue.set or array splice, as the documentation suggests:

  Vue.set(people, 0, { age: people[0].age + 1 });
  // or
  people.splice(0, 1, { age: people[0].age + 1 });

Or with immutable array, which is acceptable for non-critical places:

  let [person, ...people] = state.people;
  state.people = [{ age: person.age + 1}, ...people];

Leave a comment