I'm sorry if this is a duplicate. I have searched around for this information but I'm not really sure how best to describe it so finding it from google searches has proved very difficult.
I write Vue applications a lot and I frequently run into the following situation where I have a complex object which I would like to pass into a child component via props so that I can encapsulate its mutation nicely and then have those changes mirrored in the parent component so that all of that information is in one place.
Consider the following simple example.
// parent.vue
// list of complex objects in data
export default {
data() {
return {
tickets: [
{ name: "ticket 1", price: 20, quantity: 0 },
{ name: "ticket 2", price: 20, quantity: 0 },
// etc
]
}
}
}
// child.vue
// list passed from parent into props
export default {
props: {
tickets: Array
}
}
To obey the Vue one-way data flow best practices, I should create a local copy of the tickets prop in child. I want the local copy to be updated if the parent mutates the tickets list for any reason, so I watch the tickets prop for changes and update the local copy accordingly:
// child.vue
export default {
props: {
tickets: Array
},
data() {
return {
_tickets: []
}
},
watch: {
tickets: {
handler: function (value) {
// make deep local copy
this._tickets = clone(value);
},
deep: true,
immediate: true
}
}
}
So now we have a local deep copy that we can make changes to without violating Vue's one-way data flow. I want all of my local changes to be reflected in the parent so I deep watch the local copy and emit the changed value back up to the parent. Props down, events up. Now my child component looks like this:
// child.vue
export default {
props: {
tickets: Array
},
data() {
return {
_tickets: []
}
},
watch: {
tickets: {
handler: function (value) {
// make deep local copy
this._tickets = clone(value);
},
deep: true,
immediate: true
},
_tickets: {
handler: function () {
this.$emit('update:tickets', this._tickets);
},
deep: true
}
}
}
Now we run into an issue. This is an infinite loop because the tickets watcher mutates the _tickets property, invoking its watcher and mutating the parent value, invoking the tickets watcher... and so on. It might be possible (untested) to work around this issue by setting a flag on the ticket prop watcher so that the loop gets caught before re-emitting back up to the parent, but then this seems like a whole lot of unnecessary resources spent on creating copies just to conform with Vue best practices.
What is considered the correct approach to mutating complex object props?