Vue: complex nested objects are not reactive

Created on 23 Nov 2016  路  9Comments  路  Source: vuejs/vue

This is my first post here, if I'm not mistaken, so let me say thank you for an amazing framework which inspires us all!

I maintain a package called contextable.js. It uses schema-based objects to describe application's data model and its primary focus is to simplify server-side and client-side data validation. I've been using it in Express.js and GraphQL resolvers for now, but recently I also started using it in Vue.js through vue-contextable plugin.

I'm not sure if this is a bug report or a feature request but I kindly ask for directions on how to handle v-model reactivity in a complex object where arrays of class instances are used. I suspect that Vue.js can't track changes on arrays of class instance objects though I'm not sure what would be the difference in this case compare to an Object. I've created an example here so you can try it yourself.

Vue.js version: 2.1.0
Reproduction Link: https://github.com/xpepermint/vue-contextable-example
Steps to reproduce: Run the example and play with the provided form.
What is Expected?: The user model should be updated when input changes.
What is actually happening?: The model is not updated.

Most helpful comment

Thanks @yyx990803. I went through the documentation again and I found this paragraph:

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.

This is exactly what the problem is. I'll close this issue since I see how to solve it now. Thanks!

All 9 comments

I'm not entirely sure about the cause, but most likely it's because Vue's observer only converts an object's own properties into reactive ones (i.e. it ignores prototype properties).

Technically, if the "data" properties of your model objects are "own properties", then it should be able to work: https://jsfiddle.net/1a51gez4/

Thanks @yyx990803. I went through the documentation again and I found this paragraph:

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.

This is exactly what the problem is. I'll close this issue since I see how to solve it now. Thanks!

I'll close this issue since I see how to solve it now.

@xpepermint, can you share the way you solved the problem?
Thanks!

@maximelebreton I remember that I managed to make it work somehow, I think I was setting attributes manually within observer (on change), but at the end I decided to change the component design to the non-nested way. My projects now not don't have nested components (e.g. Table component has head,body,row but it's a single .vue file).

@yyx990803 Computed properties should be have same behaviour. Why is not working?

https://jsfiddle.net/1a51gez4/36/

@mikeevstropov computed doesn't convert the returned value. It needs to be reactive before being returned.

@yyx990803 Thank you for reply!

Thanks @yyx990803. I went through the documentation again and I found this paragraph:

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.

This is exactly what the problem is. I'll close this issue since I see how to solve it now. Thanks!

This solved my issue. I will show both to help show what he's talking about:

This is problematic:

this.modifiedAccount.billing_city = newLocation.city;
this.modifiedAccount.billing_region = newLocation.region;

That is problematic because billing_city and billing_region were not keys on modifiedAccount, so they were basically not being watched, is how I interpret that.

This is a solution to that problem:

this.modifiedAccount = {
    ...this.modifiedAccount,
    billing_city: newLocation.city,
    billing_region: newLocation.region,
};

This overwrites modifiedAccount with a copy of itself plus the new keys spread in, which will be familiar to many people, as it is a common immutable approach.

Without testing, I am pretty sure this.modifiedAccount.billing_city = newLocation.city; would trigger a re-render after you did the above solution because Vue would then be watching those keys.

EDIT: I just tested and the component did re-render after I did the above solution and then did:

this.modifiedAccount.billing_city = 'poop';

Just for reference at the forum
https://forum.vuejs.org/t/object-assign-not-triggering-reactivity/3289

Although @agm1984's solution works I simply added a {} as the first argument of Object.assign which creates a new complete object and which did the trick in terms of reactivity.
state.props = Object.assign({}, state.props, mixinprops)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bfis picture bfis  路  3Comments

hiendv picture hiendv  路  3Comments

aviggngyv picture aviggngyv  路  3Comments

loki0609 picture loki0609  路  3Comments

6pm picture 6pm  路  3Comments