3

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>
webdev778
  • 51
  • 1
  • 8
  • I'm not really following. It might be better if you could provide a proper test case with just one `v-select` and some hard-coded data. My best guess from what you've written is that you need to investigate the props `item-text` and `item-value` rather than using `reduce`. – skirtle Jul 09 '19 at 11:31
  • thanks for your comment, this is not v-select of vuetify library. it's vue-select. no props for like item-text and item-value. – webdev778 Jul 10 '19 at 12:19
  • My mistake. Nevertheless I think you're going to need to provide a more complete test case, including some sample data. – skirtle Jul 10 '19 at 15:34
  • here is a example https://jsfiddle.net/4c3tbj6m/, I want like item-text and item-value. but no props like those in VueSelect. – webdev778 Jul 10 '19 at 20:35

1 Answers1

5

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.

skirtle
  • 27,868
  • 4
  • 42
  • 57
  • this is exactly what I was looking for so far, so magnificent!!!, this is the first time posted on this platform. you inspired me. so much appreciated for your kind support. – webdev778 Jul 11 '19 at 18:35
  • @webdev778 If you appreciate his support so much, why don't you accept his answer? – Red Jun 14 '20 at 12:24