Your personalized input file (Input.vue
) needs to declare and use the value
prop that v-model
is connected to - refer to the documentation on how v-model
works with custom components
Wouldn't this only be necessary for two-way binding? From my perspective, binding the value prop simply allows the input value to be changed from the parent component
Actually, it's not quite as straightforward. The value
binding ("value from parent") is crucial every time a component is both created and reused.
Reusing existing component instances is a very common (and beneficial) optimization strategy employed by Vue. You can experiment with the example below to observe the impact of missing the value
binding on a custom input.
Components are created more frequently than you might imagine. Transitioning to a "Fixed" component and then back illustrates how dysfunctional the v-model
is without binding the value
, especially when components are dynamically generated (e.g., in Router views or within a custom "Tab" component).
I understand this may seem like a bit of a stretch - I am unsure if this resolves the issue (sharing a git repository does not align with my understanding of a Minimal, Reproducible Example) BUT it certainly indicates a bug and I don't see anything else specifically wrong with the rest of the code...
Considering how faulty a custom input is without the value
binding, it is plausible to assume that Vue developers never envisioned usage of this nature, which could lead to various "odd" and unexpected behaviors...
Vue.component('my-input-broken', {
props: ['name', 'type', 'label'],
methods: {
inputHandler(e) {
this.$emit('input', e.target.value);
},
},
template: `
<div v-if="name && type" :id="name">
<input v-if="type !== 'textarea'" @input="inputHandler" :name="name" :type="type" />
<textarea v-else-if="type === 'textarea'" @input="inputHandler" @blur="blurHandler($event)" :name="name" type="textarea" />
<label v-if="label" :for="name">{{label}}</label>
</div>
`
})
Vue.component('my-input-fixed', {
props: ['name', 'type', 'label', 'value'],
methods: {
inputHandler(e) {
this.$emit('input', e.target.value);
},
},
template: `
<div v-if="name && type" :id="name">
<input v-if="type !== 'textarea'" @input="inputHandler" :name="name" :type="type" :value='value' />
<textarea v-else-if="type === 'textarea'" @input="inputHandler" @blur="blurHandler($event)" :name="name" :value='value' type="textarea" />
<label v-if="label" :for="name">{{label}}</label>
</div>
`
})
const vm = new Vue({
el: '#app',
data: function() {
return {
values: [""],
componentToUse: 'a'
}
},
methods: {
addInput() {
this.values.unshift("")
}
},
computed: {
comp() {
return this.componentToUse === 'a' ? "my-input-broken" : "my-input-fixed"
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<div id="app">
<label for="componentToUse">Component to use:</label>
<input type="radio" name="componentToUse" v-model="componentToUse" value="a"> Broken
<input type="radio" name="componentToUse" v-model="componentToUse" value="b"> Fixed
<hr>
<button @click="addInput">Add at beginning...</button>
<component :is="comp" v-for="(value, index) in values" :key="index" v-model="values[index]" :name="`value_${index}`" type="text" :label="`value_${index} ('${values[index]}')`"></component>
</div>