I'm trying to wrap a select in a Vue custom ponent using the v-model
pattern as described in the docs.
The problem I'm facing is that I get the following error message for my custom select ponent:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent ponent re-renders. Instead, use a data or puted property based on the prop's value. Prop being mutated: "value"
found in
--->
However, when I make value
a data property, I loose the expected functionality. That is, when the bound value changes, the select box doesn't update. The two-way binding is lost.
What is the correct way to maintain the behavior I am expecting without raising warnings?
Here is an interactive example demonstrating the problem (best seen in full screen).
Vueponent('dynamic-select-ex1', {
template: '#dynamic-select-template',
props: ['value', 'options'],
methods: {
changed() {
// custom input ponents need to emit the input event
this.$emit('input', event.target.value)
},
},
})
Vueponent('dynamic-select-ex2', {
template: '#dynamic-select-template',
props: ['options'],
data() {
return {
value: null,
}
},
methods: {
changed() {
// custom input ponents need to emit the input event
this.$emit('input', event.target.value)
},
},
})
let example = new Vue({
el: '#example',
data() {
return {
selected: null,
options: [
{ text: 'Hello', value: 1 },
{ text: 'World', value: 2 },
{ text: 'Blah', value: 3 },
{ text: 'Blerg', value: 4 },
]
}
},
puted: {
text() {
if (!this.selected) return
return this.options.find(({ value }) => value == this.selected).text
},
},
methods: {
select(value) {
this.selected = value
}
}
})
<script src=".5.17/vue.js"></script>
<script type="text/x-template" id="dynamic-select-template">
<select v-model="value" @change="changed">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
</script>
<div id="example">
<label for="direct">Vue behaviour for native select</label><br>
<select id="direct" v-model="selected">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select><br>
<div>Vue behaviour for custom ponent. `value` is a prop. Warning output in console when user selects option</div>
<dynamic-select-ex1 v-model="selected" :options="options"></dynamic-select-ex1><br>
<div>Vue behaviour for custom ponent. `value` is a data property. two-way binding is broken. Selected option not updated when `value` changes.</div>
<dynamic-select-ex2 v-model="selected" :options="options"></dynamic-select-ex2><br>
<br>Selected: {{ text }}<br><br>
<button @click="select(1)">Hello</button>
<button @click="select(2)">World</button>
<button @click="select(3)">Blah</button>
<button @click="select(4)">Blerg</button><br>
</div>
I'm trying to wrap a select in a Vue custom ponent using the v-model
pattern as described in the docs.
The problem I'm facing is that I get the following error message for my custom select ponent:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent ponent re-renders. Instead, use a data or puted property based on the prop's value. Prop being mutated: "value"
found in
--->
However, when I make value
a data property, I loose the expected functionality. That is, when the bound value changes, the select box doesn't update. The two-way binding is lost.
What is the correct way to maintain the behavior I am expecting without raising warnings?
Here is an interactive example demonstrating the problem (best seen in full screen).
Vue.ponent('dynamic-select-ex1', {
template: '#dynamic-select-template',
props: ['value', 'options'],
methods: {
changed() {
// custom input ponents need to emit the input event
this.$emit('input', event.target.value)
},
},
})
Vue.ponent('dynamic-select-ex2', {
template: '#dynamic-select-template',
props: ['options'],
data() {
return {
value: null,
}
},
methods: {
changed() {
// custom input ponents need to emit the input event
this.$emit('input', event.target.value)
},
},
})
let example = new Vue({
el: '#example',
data() {
return {
selected: null,
options: [
{ text: 'Hello', value: 1 },
{ text: 'World', value: 2 },
{ text: 'Blah', value: 3 },
{ text: 'Blerg', value: 4 },
]
}
},
puted: {
text() {
if (!this.selected) return
return this.options.find(({ value }) => value == this.selected).text
},
},
methods: {
select(value) {
this.selected = value
}
}
})
<script src="https://cdnjs.cloudflare./ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/x-template" id="dynamic-select-template">
<select v-model="value" @change="changed">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
</script>
<div id="example">
<label for="direct">Vue behaviour for native select</label><br>
<select id="direct" v-model="selected">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select><br>
<div>Vue behaviour for custom ponent. `value` is a prop. Warning output in console when user selects option</div>
<dynamic-select-ex1 v-model="selected" :options="options"></dynamic-select-ex1><br>
<div>Vue behaviour for custom ponent. `value` is a data property. two-way binding is broken. Selected option not updated when `value` changes.</div>
<dynamic-select-ex2 v-model="selected" :options="options"></dynamic-select-ex2><br>
<br>Selected: {{ text }}<br><br>
<button @click="select(1)">Hello</button>
<button @click="select(2)">World</button>
<button @click="select(3)">Blah</button>
<button @click="select(4)">Blerg</button><br>
</div>
Share
Improve this question
edited Jul 14, 2022 at 2:36
tony19
139k23 gold badges277 silver badges347 bronze badges
asked Mar 10, 2021 at 5:29
br3ntbr3nt
9,6263 gold badges46 silver badges67 bronze badges
1 Answer
Reset to default 7Computed Setter
For v-model
, use a puted setter with v-model
instead of value
, to avoid mutating the prop.
You can get rid of the change
listener.
<select v-model="model">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
Vue.ponent('dynamic-select-ex1', {
template: '#dynamic-select-template',
props: ['value', 'options'],
puted: {
model: {
get() { return this.value },
set(value) { this.$emit('input', value) }
}
}
})
When the puted value is accessed, it returns the prop value, and when it's set, it emits instead.
Or :value
, @input
, and $event.target.value
Another option is to make a 1-way binding of value
and $emit
from the template:
<select :value="value" @input="$emit('input', $event.target.value)">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
Vue.ponent('dynamic-select-ex1', {
template: '#dynamic-select-template',
props: ['value', 'options'],
})