I'm using VueSelect for address select. There are 3 select components for country, state, city.
With using v-model, it's working well to get the specified value instead of option object by using reduce prop. Here my problem is to not set default value by key value.
I expected that it set the default value by id like html select.
but it's showing the key value as it is, not being bound by the label value.
Hope you can make sense from my explanation.
Expecting like v-select of vuetify.
vuetify example
<template>
<div class="col-sm-6 ">
<v-select
v-model="address.country_id"
:options="countryOptions"
:reduce="country => country.id"
label="name"
@input="resetStateAndCity"
:resetOnOptionsChange="true"
/>
</div>
<div class="col-sm-6">
<v-select
v-model="address.state_id"
:options="stateOptions"
:reduce="state => state.id"
label="name"
@input="resetCity"
:resetOnOptionsChange="true"
/>
</div>
<div class="col-sm-6">
<v-select
v-model="address.city_id"
:options="cityOptions"
:reduce="city => city.id"
label="name"
:resetOnOptionsChange="true"
/>
</div>
</template>
<script>
import VSelect from 'vue-select'
export default {
components: {
VSelect
},
data: function () {
return {
address: {
city_id: null,
state_id: null,
country_id: null
},
countryOptions: [],
stateOptions: [],
cityOptions: []
}
},
mounted() {
this.$axios.get('/countries.json')
.then(response => {
this.countryOptions = response.data
})
},
methods: {
resetStateAndCity() {
if (!this.address.country_id) return
this.$axios.get(`/countries/${this.address.country_id}/states.json`)
.then(response => {
this.stateOptions = response.data
})
this.address.state_id = null
this.address.city_id = null
this.cityOptions = []
},
resetCity() {
if (!this.address.country_id || !this.address.state_id) return
this.address.city_id = null
this.$axios.get(`/countries/${this.address.country_id}/states/${this.address.state_id}/cities.json`)
.then(response => {
this.cityOptions = response.data
})
}
}
}
</script>
I've outlined two different approaches below. Overall I think I prefer the second approach but it may depend on your circumstances which works best for you.
new Vue({
el: '#app',
components: {
'v-select': VueSelect.VueSelect
},
data: function() {
return {
input: {
user_id: 2
},
users: []
}
},
methods: {
getOptionLabel (option) {
return (option && option.name) || ''
}
},
created () {
setTimeout(() => {
this.users = [
{
id: 1,
name: "John",
last: "Doe"
},
{
id: 2,
name: "Harry",
last: "Potter"
},
{
id: 3,
name: "George",
last: "Bush"
}
]
const currentUser = this.users.find(user => user.id === this.input.user_id)
this.$refs.select.updateValue(currentUser)
}, 1000)
}
})
<link rel="stylesheet" href="https://unpkg.com/vue-select@3.1.0/dist/vue-select.css">
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-select@3.1.0/dist/vue-select.js"></script>
<div id="app">
<v-select
ref="select"
v-model="input.user_id"
:options="users"
:reduce="user => user.id"
:get-option-label="getOptionLabel"
></v-select>
</div>
A major hurdle is that Vue Select uses internal state to track the currently selected value when reduce
is used. See:
https://github.com/sagalbot/vue-select/blob/master/src/components/Select.vue#L833
Worse, the value stored in that state is not the reduced id but the whole object from the options
. So if there aren't any options
initially it gets in a right mess and it isn't particularly easy to coax it back out of that mess.
I've used a call to updateValue
to give it a kick after the options
are loaded. I've also used the prop get-option-label
rather than label
to avoid showing the number while the data is loading.
An alternative would be to only create the v-select
once the options
are available, as it works fine if they're all present when it's first created. A dummy v-select
could be used as a placeholder while waiting for the data. That looks like this:
new Vue({
el: '#app',
components: {
'v-select': VueSelect.VueSelect
},
data: function() {
return {
input: {
user_id: 2
},
users: []
}
},
created () {
setTimeout(() => {
this.users = [
{
id: 1,
name: "John",
last: "Doe"
},
{
id: 2,
name: "Harry",
last: "Potter"
},
{
id: 3,
name: "George",
last: "Bush"
}
]
}, 1000)
}
})
<link rel="stylesheet" href="https://unpkg.com/vue-select@3.1.0/dist/vue-select.css">
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-select@3.1.0/dist/vue-select.js"></script>
<div id="app">
<v-select
v-if="users.length === 0"
key="dummy"
disabled
placeholder="Loading..."
></v-select>
<v-select
v-else
v-model="input.user_id"
:options="users"
:reduce="user => user.id"
label="name"
></v-select>
</div>
The use of the key
here is important as it ensures that Vue will not reuse the same v-select
and will instead create a new one. There are other ways to achieve that but a key
is probably the cleanest.