nogridbag
nogridbag

Reputation: 3703

Vue.js - select element not updating when using :value, v-model is fine

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

Answers (3)

nogridbag
nogridbag

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

Poselsky
Poselsky

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.)

My solution

Upvotes: 0

Igor Moraru
Igor Moraru

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

Related Questions