Reputation: 3703
I have two <select>
elements. When I select a value in the first, I want to default the value for the second. It works with v-model
, but not with :value
.
One is a list of vehicles.
<select :value="vehicle" @change="setVehicle($event.target.value)">
<option value="CAR">Car</option>
<option value="PLANE">Plane</option>
</select>
The other is a list of parts. The WINGS
part is only visible if PLANE
is selected.
<select :value="part" @change="setPart($event.target.value)">
<option value="ENGINE">Engine</option>
<option value="WINDOWS">Wheels</option>
<option v-if="vehicle === 'PLANE'" value="WINGS">Wings</option>
</select>
The setter for vehicle is trivial, but the default part for a plane is the conditionally rendered WINGS
.
setVehicle: function(newVehicle) {
this.vehicle = newVehicle;
if (this.vehicle === "PLANE") {
this.part = "WINGS";
} else {
this.part = "ENGINE";
}
}
The reactive part
data is set to the correct value, e.g. you can display it with {{ part }}
, but the <select>
element is not properly updated. If I change the part's select to use v-model it works fine:
<select v-model="part">
Using v-model is not an acceptable workaround because in my real world app the part
is readonly and can only be mutated with a setter (using a store-like architecture). Another workaround is using a setTimeout(.., 1)
in the setVehicle setter. I really do not like that solution. What I really would like to know is why v-model
behaves differently than :value/@change
when the documentation suggest v-model
is simply syntactic sugar.
Demo: https://codesandbox.io/s/eager-liskov-2yor4?file=/src/App.vue
Upvotes: 1
Views: 4825
Reputation: 3703
Found an alternative workaround that may resolve similar issues:
Bind the <select>
element using a computed
property that simply returns the reactive part
property.
<select v-model="partComputed" @change="setPart($event.target.value)">
computed: {
partComputed: function () {
return this.part;
}
}
The question of why v-model behaves different than :value/@change combo is still open if anyone has insight into that. I tried Googling it, but the closest match was my own bug report I filed several years ago that I completely forgot about! I may see if this behavior is the same in Vue 3.
Upvotes: 0
Reputation: 65
Here's my solution. Basically you shouldn't be afraid of v-model. Copy the values into the component itself and use them as you please.
But I agree. :value is behaving weirdly, even if using .sync prop. (I suspect it's because of setting property vehicle to PLANE and v-if renders right after setting part WINGS. But not quite sure.)
Upvotes: 0
Reputation: 7729
Since the third option is not in the DOM yet when you are updating this.part, it is not selected at that time. So you have two option:
1) Use nextTick() to wait for component update, then set the new value:
setVehicle: function(newVehicle) {
this.vehicle = newVehicle;
this.$nextTick(()=>{
if (this.vehicle === "PLANE") {
this.part = "WINGS";
} else {
this.part = "ENGINE";
}
})
}
or,
2) Keep the third option rendered, but hide it when the vehicle is car. In such way it will get selected, at the moment of changing the vehicle, and showed later when the DOM will be updated. You will do it with v-show instead of v-if.
<select :value="part" @change="setPart($event.target.value)">
<option value="ENGINE">Engine</option>
<option value="WINDOWS">Wheels</option>
<option v-show="vehicle === 'PLANE'" value="WINGS">Wings</option>
</select>
Upvotes: 5