@mariohbrino is right, use the .sync modifier
The only extra clarification I'd add is do not directly modify your props.
Here's an example from some code I have in production:
In the parent component:
<tag-picker :tags.sync="tags" />
In the child component (TagPicker):
export default {
props: {
tags: { type: Array, default: () => [] },
},
data() {
return {
internalTags: [], // used internally, as the v-model
};
},
// This watcher fires immediately, so we don't need a created() hook
// Every time the tags prop is updated, the internalTags get synced
// so state is properly maintained
watch: {
tags: {
handler(newValue) {
this.internalTags = [...newValue];
},
immediate: true,
},
},
methods: {
async addTag(tag) {
// some stuff is done here to validate/ensure proper format/etc.. then:
this.$emit('update:tags', this.internalTags);
},
};
I cut out all the other stuff going on in this component just to focus on the part that's relevant to this discussion.
Since I passed in the tags prop with the .sync modifier, vue is expecting an update:tags event to be emit with a new value for that prop, when that occurs, the value is updated within the parent, which is then naturally passed back into the child component, at which point the watcher sees it and re-syncs the internal data value the child component is able to manipulate.
addTag() in this case is triggered when a user has added a new tag to an input field and hit return to 'submit' it.