Using v-model with custom components
The Docs way
The documentation is clear, we have to do it this way to use v-model
with a custom component:
<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" />
export default {
props: ['value']
};
And the parent:
<div>
<Child v-model="value"></Child>
{{ value }}
</div>
import Child from './Child.vue';
export default {
components: {
Child
},
data() {
return {
value: ''
};
}
};
But what if we are creating another component that is going to be reused elsewhere? We'll have a Grandparent component that contains the Parent one, so now it will look like:
import Child from './Child.vue';
export default {
components: {
Child
},
// Props instead of data()
props: ['value']
};
And what is happening right now? We are getting the Vue warn:
But why? The value
variable inside Parent.vue is not part of the data object but instead a prop. So, we can say it is owned now by a Grandparent component (using v-model).
Now, let's recall that v-model="value"
it's the same as v-bind:value="value" v-on:input="value = $event"
so, when the Child.vue component emits the input event, Parent.vue executes v-on:input="value = $event"
changing the value inside a child component in regard to the Grandparent one, hence the Vue warn.
Computed Property as solution
The Vue warn tells us to use a data or computed property based on the prop's value to fix this, but how?
tl;dr:
<div>
<Child v-model="parentValue"></Child>
{{ parentValue }}
</div>
import Child from './Child.vue';
export default {
components: {
Child
},
props: ['value'],
computed: {
parentValue: {
get() {
return this.value;
},
set(setterValue) {
this.$emit('input', setterValue);
}
}
}
};
What is happening now is that when the Child.vue component emits the input event, Parent.vue changes the value of the parentValue variable, but because it is a computed value, it triggers another input event, and now the Grandparent component is the one that handles the real change. We have to notice that the getter of the computed value returns the value
prop so we'll always have the updated value from the parent component (Grandparent in this case).
A full example
Now, that we know the behaviour of computed properties with v-model we can create components over other components over another component ad infinitum.
This example has a Child, Parent and Grandparent components within a Great-grandparent as the page where they reside. App.vue has the reactive property and the other components use only props and computed properties. We can change the value on either of the components without getting the Vue warn.
The Vue CLI was used to create the main project.
Child:
<div class="component-css">
Child:
<input v-model="childValue" />
{{ childValue }}
</div>
export default {
props: ['value'],
computed: {
childValue: {
get() {
return this.value;
},
set(setterValue) {
this.$emit('input', setterValue);
}
}
}
};
Parent:
<div class="component-css">
Parent:
<input v-model="parentValue" />
{{ parentValue }}
<Child v-model="parentValue"></Child>
</div>
import Child from './Child.vue';
export default {
components: {
Child
},
props: ['value'],
computed: {
parentValue: {
get() {
return this.value;
},
set(setterValue) {
this.$emit('input', setterValue);
}
}
}
};
Grandparent:
<div class="component-css">
Grandparent:
<input v-model="grandparentValue" />
{{ grandparentValue }}
<Parent v-model="grandparentValue"></Parent>
</div>
import Parent from './Parent.vue';
export default {
components: {
Parent
},
props: ['value'],
computed: {
grandparentValue: {
get() {
return this.value;
},
set(setterValue) {
this.$emit('input', setterValue);
}
}
}
};
App:
<div id="app" class="component-css">
App:
<input v-model="someString" />
{{ someString }}
<Grandparent v-model="someString"></Grandparent>
</div>
import Grandparent from './components/grandparent.vue';
export default {
name: 'App',
components: {
Grandparent
},
data() {
return {
someString: ''
};
}
};
.component-css {
margin: 6px;
padding: 6px;
border: 1px dashed rgb(131, 131, 131);
}