Vuejs Data/Props/Computed Not Updating And Change Detection Caveats

Dec 15, 2017

Pass in object or array for reactivity/detect data changes

The following examples pass in 3 kind of variable as data:

  • user (object with property)
  • count (integer)
  • numbers (array)
let user = {  id: 0}let count = 0let numbers = [0]const vm = new Vue({  data: {    user: user,    count: count,    numbers: numbers  },  computed: {    userId() {      return this.user.id    }  }})

To maintain reactivity/change detection between Vue and external source, you should only pass in object (modify properties) or array (modify array).

user.id = 100console.log(vm.user.id)     // Output: 100console.log(vm.userId)      // Output: 100numbers.push(99)console.log(vm.numbers)     // Output: [0, 99]numbers[0] = 88console.log(vm.numbers)     // Output: [88, 99]

Note: It is recommended to modify array using Vue.set(numbers, 0, 88) rather than numbers[0] = 88 in order for it to work in v-for list.

If you modify the variable directly, the changes can't be deteced.

count = 1console.log(vm.count)       // Output: 0 (Not Updated)user = { id: 1 }console.log(vm.user.id)     // Output: 100 (Not Updated)console.log(vm.userId)      // Output: 100 (Not Updated)numbers = [33, 44]console.log(vm.numbers)     // Output: [88, 99] (Not Updated)

Use Vue.set to add dynamic property to object

JavaScript cannot detect addition or deletion of property, so it's preferable to pre-declare all data properties.

const user = {  id: 0}const vm = new Vue({  data: {    user: user  },  computed: {    userId() {      return this.user.id    },    userAge() {      return this.user.age    },    userWeight() {      return this.user.weight    }  }})

Observations:

  • user.id work as expected because it is a pre-declared property
  • user.weight work as expected because we use Vue.set to add property.
  • user.age had unexpected result for computed userAge because .age property is added on runtime directly (not via Vue.set). If you already added a runtime property directlty, subsequent call to Vue.set has no effect to restore reactivity.
user.id = 100console.log(user.id)        // Output: 100console.log(vm.user.id)     // Output: 100console.log(vm.userId)      // Output: 100vm.user.id = 99console.log(user.id)        // Output: 99console.log(vm.user.id)     // Output: 99console.log(vm.userId)      // Output: 99Vue.set(user, 'weight', 60) // use this to add new propertyconsole.log(user.weight)    // Output: 60console.log(vm.user.weight) // Output: 60console.log(vm.userWeight)  // Output: 60user.weight = 59console.log(user.weight)    // Output: 59console.log(vm.user.weight) // Output: 59console.log(vm.userWeight)  // Output: 59user.age = 70console.log(user.age)       // Output: 70console.log(vm.user.age)    // Output: 70console.log(vm.userAge)     // Output: 70user.age = 69console.log(user.age)       // Output: 69console.log(vm.user.age)    // Output: 69console.log(vm.userAge)     // Output: 70 (Not Updated)Vue.set(user, 'age', 68)    // too late as age property is already added directlyconsole.log(user.age)       // Output: 68console.log(vm.user.age)    // Output: 68console.log(vm.userAge)     // Output: 70 (Not Updated)

Copy multiple properties to object

Vue.js recommend the following:

Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object:

// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

If Object.assign applied on vm.user (not original user) to create fresh object (using {}), and the original user object outside of Vue is not updated (since we create a fresh object within Vue).

const user = {  id: 0}const vm = new Vue({  data: {    user: user  },  computed: {    userId() {      return this.user.id    },    userName() {      return this.user.name    }  },})vm.user = Object.assign({}, vm.user, { id: 7, name: 'Desmond Lua', height: 186 })console.log(user.id)        // Output: 0 (Not Updated)console.log(vm.user.id)     // Output: 7console.log(vm.userId)      // Output: 7console.log(user.name)      // Output: undefined (Not Updated)console.log(vm.user.name)   // Output: Desmond Luaconsole.log(vm.userName)    // Output: Desmond Luavm.user.name = 'Sam Lee'console.log(user.name)      // Output: undefined (Not Updated)console.log(vm.user.name)   // Output: Sam Leeconsole.log(vm.userName)    // Output: Sam Leeuser.name = 'Albert Hitch'console.log(user.name)      // Output: Albert Hitchconsole.log(vm.user.name)   // Output: Sam Lee (Not Updated)console.log(vm.userName)    // Output: Sam Lee (Not Updated)

If Object.assign on vm.user without creating a fresh object, computed shall not be updated (same effect as not using Vue.set).

const user = {  id: 0}const vm = new Vue({  data: {    user: user  },  computed: {    userId() {      return this.user.id    },    userName() {      return this.user.name    }  }})vm.user = Object.assign(vm.user, { id: 7, name: 'Desmond Lua', height: 186 })console.log(user.id)        // Output: 7console.log(vm.user.id)     // Output: 7console.log(vm.userId)      // Output: 7console.log(user.name)      // Output: Desmond Luaconsole.log(vm.user.name)   // Output: Desmond Luaconsole.log(vm.userName)    // Output: Desmond Lua (First access cache)vm.user.name = 'Sam Lee'console.log(user.name)      // Output: Sam Leeconsole.log(vm.user.name)   // Output: Sam Leeconsole.log(vm.userName)    // Output: Desmond Lua (Not Updated)user.name = 'Albert Hitch'console.log(user.name)      // Output: Albert Hitchconsole.log(vm.user.name)   // Output: Albert Hitchconsole.log(vm.userName)    // Output: Desmond Lua (Not Updated)

If Object.assign applied on original user (not vm.user) to create fresh object (using {}), only the original user object outside of Vue is updated (since it's a fresh object), wm.user shall not be updated.

let user = {  id: 0}const vm = new Vue({  data: {    user: user  },  computed: {    userId() {      return this.user.id    },    userName() {      return this.user.name    }  }})user = Object.assign({}, user, { id: 7, name: 'Desmond Lua', height: 186 })console.log(user.id)        // Output: 7console.log(vm.user.id)     // Output: 0 (Not Updated)console.log(vm.userId)      // Output: 0 (Not Updated)console.log(user.name)      // Output: Desmond Luaconsole.log(vm.user.name)   // Output: undefined (Not Updated)console.log(vm.userName)    // Output: undefined (Not Updated)vm.user.name = 'Sam Lee'console.log(user.name)      // Output: Desmond Lua (Not Updated)console.log(vm.user.name)   // Output: Sam Leeconsole.log(vm.userName)    // Output: undefined (Not Updated)user.name = 'Albert Hitch'console.log(user.name)      // Output: Albert Hitchconsole.log(vm.user.name)   // Output: Sam Lee (Not Updated)console.log(vm.userName)    // Output: undefined (Not Updated)

If Object.assign on original user (not vm.user) without creating a fresh object, computed shall not be updated (same effect as not using Vue.set).

let user = {  id: 0}const vm = new Vue({  data: {    user: user  },  computed: {    userId() {      return this.user.id    },    userName() {      return this.user.name    }  }})user = Object.assign(user, { id: 7, name: 'Desmond Lua', height: 186 })console.log(user.id)        // Output: 7console.log(vm.user.id)     // Output: 7console.log(vm.userId)      // Output: 7console.log(user.name)      // Output: Desmond Luaconsole.log(vm.user.name)   // Output: Desmond Luaconsole.log(vm.userName)    // Output: Desmond Lua (First access cache)vm.user.name = 'Sam Lee'console.log(user.name)      // Output: Sam Leeconsole.log(vm.user.name)   // Output: Sam Leeconsole.log(vm.userName)    // Output: Desmond Lua (Not Updated)user.name = 'Albert Hitch'console.log(user.name)      // Output: Albert Hitchconsole.log(vm.user.name)   // Output: Albert Hitchconsole.log(vm.userName)    // Output: Desmond Lua (Not Updated)

Conclusion: Follow Vue.js recommendedation to apply Object.assign applied on vm.user (not original user) to create fresh object (using {}) is the best choice, but it won't update the original user. If you need to update the original user as well, you should either:

  • Pre-declared all object properties
  • Use Vue.set instead of Object.assign
  • Update original user using Event Bus

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.