5

Why v-for doesn't react to change some element in array?

this.test[3] = 3

If later I will call push for array, only then Vue rerender content.

this.test[3] = 3;
this.push(4);

Example: https://codepen.io/anon/pen/debJqK

var vm = new Vue({
  el: "#app",

  data: {
    test: [1, 2],
  },
  methods: {
    changeVar1: function() {
      this.test[3] = 3;
      this.test.push(4);
    },
    changeVar2: function() {
      this.test[3] = 3;
    }
  }
})
html * {
  box-sizing: border-box;
}

body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding: 4em;
}
<script src="https://cdn.jsdelivr.net/vue/1.0.26/vue.min.js"></script>

<div id="app">
  <button @click="changeVar1()">Work</button>
  <button @click="changeVar2()">Doesn't work</button>
  <p v-for="(index, digit) in test">{{digit}}</p>
</div>

1
  • 1
    I would like to redirect you also to this where it explains the reactivity of the DOM regarding either object or array changes. Commented Dec 17, 2019 at 9:53

1 Answer 1

12

If you add elements by index, you have to call Vue.set():

Vue.set(this.test, 3, 3);

Or:

this.test.splice(3, 1, 3)

This enables Vue to adjust the reactivity to that element. Updated codePen.

Why?

Besides regular caveat problems, the docs have a specific guidance on arrays:

Caveats

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

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

For example:

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

To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:

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

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

You can also use the vm.$set instance method, which is an alias for the global Vue.set:

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

To deal with caveat 2, you can use splice:

vm.items.splice(newLength)

Updated stack snippet:

var vm = new Vue({
  el: "#app",

  data: {
    test: [1, 2],
  },
  methods: {
    changeVar1: function() {
      this.test[3] = 3;
      this.test.push(4);
    },
    changeVar2: function() {
      Vue.set(this.test, 3, 3);
      // this.test.splice(3, 1, 3); // would work the same
    }
  }
})
html * {
  box-sizing: border-box;
}

body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding: 4em;
}
<script src="https://cdn.jsdelivr.net/vue/1.0.26/vue.min.js"></script>

<div id="app">
  <button @click="changeVar1()">Work</button>
  <button @click="changeVar2()">Didn't work, now fixed</button>
  <p v-for="(index, digit) in test">{{digit}}</p>
</div>

Sign up to request clarification or add additional context in comments.

2 Comments

Upvoted! But can you extend on this? "Why" is more important that "How"
@samayo read the link Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.