Let's say we have a variable called someVar in our component - it is properly defined and its initial value is 1:
...
data: {
someVar: 1
}
...
In our template there is a select tag with 3 options bound to someVar with v-model:
<select v-model="someVar">
<option value="1">Option 1</option>
<option value="3">Option 3</option>
<option value="5">Option 5</option>
</select>
At this point everything works as expected. However, if we then change the value of someVar to a value that is not covered by any of the option tags (2 in this example), v-model will set someVar to undefined instead:
...
created: function() {
setTimeout(function(){
this.someVar = 2;
// this.someVar becomes undefined instead
}.bind(this), 2000);
}
...
Here is a jsfiddle showing the issue:
https://jsfiddle.net/0vpqny9y/4/
What I expected to happen was that the value is set properly and the select tag becomes empty.
While the setTimeout example may be silly, where it's becoming a pain to me is when I load both the options and the model that has a property bound to the select at the same time, which results in a race condition:
1) Options load first, model loads second, the option is present so everything works fine.
2) Model loads first, the bound value is set to whatever was loaded through AJAX, but since there are no options yet the value is instead set to undefined, then the options load by this time the original value is lost
Thanks @kordian-kowalski , I think this is an expected behavior.
Native html select also behaves like that, the value will be empty if you set an 'invalid' value to it.
Vue.js will force to update the select and calculate the value when you assign a new value to it, so it becomes undefined.
You might need to process it in the userland.
Hmm, any chance to make a feature request for this to be handled by Vue instead? I think it would make the templates nicer if there was no need to work around such a common (I think?) quirk :)
Especially since just initializing the value without changing it later works fine:
https://jsfiddle.net/4h6ysyvf/
The value is preserved here. And of course if the initial value happens to be the same as the one I'm trying to assign to it, nothing breaks:
https://jsfiddle.net/4h6ysyvf/1/
Those examples work because no events are triggered when the variable is initialized with a value, and when it's updated to it's current value. I don't think there's a perfect way to handle this consistently, so I'd suggest following @defcc and implementing it yourself on a case by case basis.
Native <select>
does not fire a change
event when setting the property to an invalid value (jsfiddle), so I think it is a bit unexpected that setting a property in Vue would cause it to immediately overwrite the same property.
I was also confused by this. If this is the expected behavior, it may be helpful to mention this in the Form Input Bindings -> Select section of the documentation.
As @defcc said, you can implement this behavior manually, by watching anotherVal.
"select" bind with "selectedVal" by v-model, "input" bind with "anotherVal" by v-model, "selectedVal" bind "anotherVal" with each other by watch, except selectedVal changes to undefined
jsfiddle
Not sure if this is the "right" way, but I got around this issue with async options by using v-if
on the component to not render it until the async operation was complete.
A v-if
to check if there's any element in the list solves the issue:
<select v-if="items.length">
<option v-for="item in items">{{item}}</option>
</select>
I still consider this an issue, though. I think this hack should not be necessary.
A good way of having a default value is adding a disabled option:
<select v-model="input">
<option v-if="input == null" disabled :value="null">Select an option</option>
...
</select>
http://jsfiddle.net/oov159xx/12
We're always open to improve the docs on vuejs/vuejs.org with PR 馃檪
Closing this since this it's a documentation issue and because we cannot change this for v2 because it will be a breaking change. We will revisit it for next major release though
This thread saved my life :)
@paulredmond Thank you! Was seeing some strange async issues with IE 11 that I could not recreate in Chrome/Edge etc. Using the loaded true/false check on my form before executing v-for fixed it!
Did this behavior change with 2.5.0? Because, now the jsfiddle in OP works like expected (model does not become undefined). But for versions before 2.5.0, it works as described in the OP (model does become undefined).
I have a similar problem, but my code is slightly different. I not only change the model, but the options as well. Here is a jsfiddle of the problem:
https://jsfiddle.net/hc9r18ot/
In this case, the model is still set as undefined even with versions > 2.5.0. If I don't change both of the values at the same time, then there is no problem. For example, if I change model first and 1 second later change the options (via setTimeout), then the value does not become undefined. Similarly if I change the options first and change the model after a second the model again does not become undefined.
Is this the expected behavior?
PS: Thank you for opening this issue, I've been struggling with it for 2 days and I would have never found what changes my model to undefined.
I am having this problem and no solution seems to be working.
My case it's a bit different: I've got a select whose options are populated with v-for
<select id="stage" v-model="newItem.stage">
<option v-if="newItem.stage == null || !newItem.stage" disabled :value="null">-</option>
<option v-for="s in stages" :key="s.id" :value="s.id">{{ s.name }}</option>
</select>
The first time I load the component everything works as expected.
After I save the item, I reset the input by unsetting all newItem
. Only then the select doesn't work on change anymore and I need to change it twice for the value to appear. (Getting undefined the first time)
Could somebody please help?
Issues we had with this were solved when we moved the value-binding to the options, instead of the select:
<select
:disabled="disabled"
@change="updateInput"
>
<option disabled selected value>{{ placeholder }}</option>
<option
v-for="(optie,i) in optieObjects"
:value="optie.value"
:disabled="optie.disabled"
:selected="optie.value == value"
>{{ optie.label }}
</option>
</select>
Most helpful comment
Not sure if this is the "right" way, but I got around this issue with async options by using
v-if
on the component to not render it until the async operation was complete.https://jsfiddle.net/paulredmond/a085bpmn/2/