5

I'm trying to wrap a select in a Vue custom component using the v-model pattern as described in the docs.

The problem I'm facing is that I get the following error message for my custom select component:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"

found in

--->

However, when I make value a data property, I loose the expected functionality. That is, when the bound value changes, the select box doesn't update. The two-way binding is lost.

What is the correct way to maintain the behavior I am expecting without raising warnings?

Here is an interactive example demonstrating the problem (best seen in full screen).

Vue.component('dynamic-select-ex1', {
  template: '#dynamic-select-template',
  props: ['value', 'options'],
  methods: {
    changed() {
      // custom input components need to emit the input event
      this.$emit('input', event.target.value)
    },
  },
})

Vue.component('dynamic-select-ex2', {
  template: '#dynamic-select-template',
  props: ['options'],
  data() {
    return {
      value: null,
    }
  },
  methods: {
    changed() {
      // custom input components need to emit the input event
      this.$emit('input', event.target.value)
    },
  },
})

let example = new Vue({
  el: '#example',
  data() {
    return {
      selected: null,
      options: [
        { text: 'Hello', value: 1 },
        { text: 'World', value: 2 },
        { text: 'Blah', value: 3 },
        { text: 'Blerg', value: 4 },
      ]
    }
  },
  computed: {
   text() {
     if (!this.selected) return
     return this.options.find(({ value }) => value == this.selected).text
   },
  },
  methods: {
    select(value) {
      this.selected = value
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<script type="text/x-template" id="dynamic-select-template">
  <select v-model="value" @change="changed">
    <option v-for="option in options" :value="option.value">{{ option.text }}</option>
  </select>
</script>

<div id="example">
  <label for="direct">Vue behaviour for native select</label><br>
  <select id="direct" v-model="selected">
    <option v-for="option in options" :value="option.value">{{ option.text }}</option>
  </select><br>

  <div>Vue behaviour for custom component. `value` is a prop. Warning output in console when user selects option</div>
  <dynamic-select-ex1 v-model="selected" :options="options"></dynamic-select-ex1><br>

  <div>Vue behaviour for custom component. `value` is a data property. two-way binding is broken.  Selected option not updated when `value` changes.</div>
  <dynamic-select-ex2 v-model="selected" :options="options"></dynamic-select-ex2><br>
  
  <br>Selected: {{ text }}<br><br>
  
  <button @click="select(1)">Hello</button>
  <button @click="select(2)">World</button>
  <button @click="select(3)">Blah</button>
  <button @click="select(4)">Blerg</button><br>

</div>

1 Answer 1

7

Computed Setter

For v-model, use a computed setter with v-model instead of value, to avoid mutating the prop. You can get rid of the change listener.

<select v-model="model">
  <option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
Vue.component('dynamic-select-ex1', {
  template: '#dynamic-select-template',
  props: ['value', 'options'],
  computed: {
    model: {
      get() { return this.value },
      set(value) { this.$emit('input', value) }
    }
  }
})

When the computed value is accessed, it returns the prop value, and when it's set, it emits instead.

Or :value, @input, and $event.target.value

Another option is to make a 1-way binding of value and $emit from the template:

<select :value="value" @input="$emit('input', $event.target.value)">
  <option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
Vue.component('dynamic-select-ex1', {
  template: '#dynamic-select-template',
  props: ['value', 'options'],
})
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! That works exactly as v-model does on a native select :)
You're welcome :) I added another option that doesn't use v-model
That second option is much cleaner :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.