Home > Enterprise >  Vue 3 - V-Model Confusion
Vue 3 - V-Model Confusion

Time:05-08

I am teaching myself vue 3. I have read article after article on v-model and each time I think I understand how it works I get confused again.

My goal: I built a custom dropdown component. I need the ability to control the value of this dropdown from the parent. When the dropdown changes I want to let the parent know the new value and the index.

Child component.vue

  <div>
    <select
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
      <option v-for="option in options" :key="option">
        {{ option }}
      </option>
    </select>
  </div>
</template>

<script>
export default {
  props: ["options", "modelValue"],

  emits: ["update:modelValue"],

  methods: {
    selected() {

//??????
//want to emit this to the parent
      let selectedIndex = this.$event.target.selectedIndex   1
      //this.$emit(value, selectedIndex)
    },
  },
};
</script>

parent.vue

<template>
  <my-drop-down :options="options" v-model="selectedOption" />
 
</template>

<script>
import myDropDown from "./components/base_dropdown.vue";

export default {
  name: "App",

  data: () => ({
    selectedOption: "2 Line",
    selectedIndex: 0,
    options: ["1 Line", "2 Line", "3 Line"],
  }),

  components: {
    myDropDown,
  },

  methods: {
    //How can I call this when the select value changes??
    onSelectChange(selected, index) {
      console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
    },

  },
};
</script>

The two way binding is working correctly. I can control the value of the dropdown from either the child or the parent. But how do I call the onSelectChange method in my child component

Also, and this is may be a dumb question but...

v-model="selectedOption" is the same as writing :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"

so why is the parent written like this <my-drop-down :v-model="selectedOption" />

and the child written like this <select :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">

and not simply <select :v-model="selectedOption />

CodePudding user response:

You look to be needing a watcher in your parent component, one that watches for changes to the selectedOption property, and then uses the new value to get the index from the options array and adds one to it, and uses the new value to set the selectedIndex property.

Per the Vuejs API section on Watchers:

Computed properties allow us to declaratively compute derived values. However, there are cases where we need to perform "side effects" in reaction to state changes - for example, mutating the DOM, or changing another piece of state based on the result of an async operation.

With Options API, we can use the watch option to trigger a function whenever a reactive property changes.

So, for your code, it might look something like:

  watch: {
    selectedOption(newValue, oldValue) {
      console.log('In watcher. New value: '   newValue)
      
      // get the index of the String in the array and use it to set
      // the selectedIndex:
      this.selectedIndex = this.options.findIndex(i => i === newValue)   1;
      console.log('selectedIndex: '   this.selectedIndex)
    }
  },

As for your question's "second part",

why is the parent written like this <my-drop-down :v-model="selectedOption" />

and the child written like this <select :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">

and not simply <select :v-model="selectedOption />

It is probably best to ask that as a separate question, in order to maintain question specificity as required by the site, but as I see it, selectedOption is not acting as a model for the select tag, and in fact selectedOption isn't even a property of the child component, nor should it be.

CodePudding user response:

If you want to call a method inside your parent component when the "select value changes", It is better to call it inside a Vue watch like the codes below:

Parent component:

<template>
  <my-drop-down :options="options" v-model="selectedOption" />
</template>

<script>
import myDropDown from "../components/baseDropdown.vue";
export default {
  name: "parentModel",
  data: () => ({
    selectedOption: "2 Line",
    // selectedIndex: 0,
    options: ["1 Line", "2 Line", "3 Line"],
  }),

  components: {
    myDropDown,
  },

  computed: {
    /* It is better to use computed property for "selectedIndex", because it is related to "selectedOption" and changes accordingly. */
    selectedIndex: function () {
      return this.options.indexOf(this.selectedOption)
    }
  },

  watch: {
    selectedOption(newSelect, oldSelect) {
      this.onSelectChange(this.selectedOption, this.selectedIndex)
    }
  },

  methods: {
    //How can I call this when the select value changes??
    onSelectChange(selected, index) {
      console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
    },

  },
}
</script>

<style scoped>

</style>

Child component:

<template>
  <div>
    <select
        :value="modelValue"
        @change="$emit('update:modelValue', $event.target.value)"
    >
      <!-- You can use v-model also here. But it only changes the value of "modelValue" and does not emit anything to parent component. -->
<!--    <select v-model="modelValue">-->
      <option v-for="option in options" :key="option">
        {{ option }}
      </option>
    </select>
  </div>
</template>

<script>
export default {
  name: "baseDropdown",
  props: ["options", "modelValue"],

  emits: ["update:modelValue"],

  /* --------------------------------- */
  /* You don't need this method, because "$emit('update:modelValue', $event.target.value)" that is used in "select" tag itself is enough to emit data to the parent component. */
  /* --------------------------------- */

//   methods: {
//     selected() {
//
// //??????
// //want to emit this to the parent
//       let selectedIndex = this.$event.target.selectedIndex   1
//       //this.$emit(value, selectedIndex)
//     },
//   },
}
</script>

<style scoped>

</style>

And about your second part of the question:

v-model="selectedOption" is the same as writing :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"

In my opinion it is not a true statement for two reasons:

  1. Reason one: according to Vue docs :

v-model="selectedOption" is the same as writing :value="selectedOption" @input="event => selectedOption = event.target.value"

you can't see any $emit in the above statement. But in your case you want to emit data to the parent component.

  1. Reason two: again according to Vue docs it is better to use change as an event for <select> tag.
  • Related