Vue-select: v-model with reduce if value changed outside v-select

Created on 24 Apr 2019  路  22Comments  路  Source: sagalbot/vue-select

Describe the bug
When using v-model with reduce and changing v-model value in vue component v-select value is not updated. Input event not emitted.

To Reproduce
Pick any value from dropdown
Press reset button
https://codepen.io/anon/pen/PgyLXz

<div id="app">
  <h1>Vue Select</h1>
  <v-select v-model="selectedValue" 
            :options="options"
            :reduce="option => option.val">
  </v-select>
  <button @click="resetDropdown">reset</button>
</div>

<script src="https://unpkg.com/vue@latest"></script>
<script src="https://unpkg.com/vue-select@latest"></script>
Vue.component('v-select', VueSelect.VueSelect)

new Vue({
  el: '#app',
  data: {
      selectedValue: null,
      options: [
        { label: "one", val: 1 },
        { label: "two", val: 2 },
        { label: "three", val: 3 }
      ]
  },
   methods: {
    resetDropdown() {
      this.selectedValue = null;
    }
  },
  updated() {
    console.log(this.selectedValue);
  }
})

Expected behavior
V-select value is changed with associated v-model variable.
input event emitted

bug

Most helpful comment

I don't know why this problem has not been resolved so far, but we can solve it simply by setting a key to component

  <v-select
    v-model="selected"
    :options="options"
    :key="selected"
    :reduce="op => op.id"
  />
new Vue({
  el: '#app',
  data: {
    selected: 3,
    options: [
      { id: 1, label: 'op-1' },
      { id: 2, label: 'op-2' },
      { id: 3, label: 'op-3' },
      { id: 4, label: 'op-4' },
      { id: 5, label: 'op-5' }
    ]
  }
})

All 22 comments

Thanks for reporting and providing a repro link. I'll get a patch out for this one.

Forked: https://codepen.io/anon/pen/zXMNRj#anon-login

Notes:

  • I think a possible solution is to add a watcher to selectedValue computed prop

Duplicate #842

Do you have a workaround for this issue whilst you are working on a patch?
Thanks

I have tried to update in code and working
Just add this function in watch

watch:{
      value(){
        this.updateValue(this.findOptionFromReducedValue(this.value) || {})
      }
}

That's all!

Hope @sagalbot help to update it.

Forked: https://codepen.io/anon/pen/zXMNRj#anon-login

Notes:

* I think a possible solution is to add a watcher to `selectedValue` computed prop

I've try to do that but with no success.
Any comment on the @huypn patch ?

Any update on this? This is kind of a blocker right now. The proposal by huypn didn't solve it for me, made it worse by triggering watchers again.

the best workaround I've found for this issue is to not use the reduce attribute and change the v-model bound attribute to be a computed property that does the necessary conversion. Here's an example that I implemented in my project:

import { keyBy } from 'lodash'

export default {
  data: () => ({
    selectedTagIds: []
  }),
  computed: {
    selectedTags: {
      get() {
        // optimize with lodash.keyBy to only loop through data once
        const tagsById = keyBy(this.tags, '_id')
        return this.selectedTagIds.map(id => tagsById[id])
      },
      set(tags) {
        this.selectedTagIds = tags.map(tag => tag._id)
      }
    }
  }
}

@edwardsph

Here's a nice little utility version:

// compute-objects-to-ids.js
import { keyBy, get, set } from 'lodash'

export default function computeObjectsToIds({ source, dest, key }) {
  return {
    get() {
      const byId = keyBy(get(this, source), i => get(i, key))
      const destProp = get(this, dest)
      if (Array.isArray(destProp)) {
        return destProp.map(i => byId[i])
      } else {
        return []
      }
    },
    set(items) {
      set(this, dest, items.map(i => get(i, key)))
    }
  }
}

And you use it like this:

import computeObjectsToIds from './compute-objects-to-ids'

export default {
  data: () => ({
    selectedTagIds: []
  }),
  computed: {
    selectedTags: computeObjectsToIds({
      source: 'tags',
      dest: 'selectedTagIds',
      key: '_id'
    }),
  }
}

Once you've got it setup, only pass the array of objects to the v-model of vue-select.

Note that it's using lodash.get and lodash.set, internally, so you can set all of the values to any nested prop available on this. If you want to set on an object in data which contains a tagIds attribute, use dest: 'myObject.tagIds'.

I'm eager to see this patched as well! This is an unfortunate bug on an otherwise extremely useful feature.

I think one quick fix is for me is, I have created options in beforeMount callback instead in mounted callback

@rajilesh I am populating my select in created() hook (which should come before beforeMount()) and it doesn't work anyway.

I don't know why this problem has not been resolved so far, but we can solve it simply by setting a key to component

  <v-select
    v-model="selected"
    :options="options"
    :key="selected"
    :reduce="op => op.id"
  />
new Vue({
  el: '#app',
  data: {
    selected: 3,
    options: [
      { id: 1, label: 'op-1' },
      { id: 2, label: 'op-2' },
      { id: 3, label: 'op-3' },
      { id: 4, label: 'op-4' },
      { id: 5, label: 'op-5' }
    ]
  }
})

@talkhabi Thank you, it works like a charm!

The :key fix works great for single selects!

However, it seems to have some unintended side-effects on multiple selects. Specifically, I noticed that the :close-on-select="false" property doesn't work anymore.
See https://codepen.io/anon/pen/orVdYe

To add to what @LRWeber writes: With multiple selects there's also the problem that Vue complains about key not being a simple type (it's array).

@borekl Good catch! I've now managed to resolve that error using :key="selected.join('-')". (Though the side effects still persist.)

3 months now... :-(

@geri777 You probably didn't see this announcement yet.

I got a fix in #914 which in tracks the value property and updates the internal representation correctly. It does not use updateValue because there should be no input event in this case.

In addition it makes sure the correct option is selected. See #914 for more information.

Thanks @doits for the great PR! Getting a couple other PRs merged and will tag a release.

<v-select v-model="selectedCompanyId" label="Select Company" dense outlined :key="selectedCompanyId" :items="companies" item-text="companyName" item-value="companyId" > </v-select>

declare below under data : {}

`declare below in data

companies: [
{ companyId: '1', companyName: 'One' },
{ companyId: '2', companyName: 'Two' },
{ companyId: '3', companyName: 'Three' }
]`

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jluterek picture jluterek  路  3Comments

FrancescoMussi picture FrancescoMussi  路  3Comments

pud1m picture pud1m  路  3Comments

twz915 picture twz915  路  3Comments

rudykaze picture rudykaze  路  3Comments