Home > Software engineering >  Using ref with props
Using ref with props

Time:05-26

I have a Component A with a nested Component B. Component A loads a list of strings, Component B has a SELECT element, which is populated when Component A is finished loading. The first option of the SELECT should be selected at this moment. After this moment, when other options are selected, Component B should emit update event with selection data, so that Component A 'knows' what option was selected. Since A should control B, this option is passed back to B with props.

In my implementation below, only the initial value of the selection is received by Component B, and when the list is loaded, it is populated successfully, but the selectedOption value is not changed by the controlling property. So, it doesn't work.

What would be a proper way to implement this?

Component A:

<template>
  <component-b :list="list" :selected="selected" @update="onUpdate">
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
  setup: () => {
    const list = ref<string[]>([]);
    const selected = ref('');

    load().then(data => {       // some load function returning a promise
      selected.value = data[0]; // data is never empty
      list.value = data;
    });

    const onUpdate = (data: string) => selected.value = data;

    return { list, selected, onUpdate };
  }
})
</script>

Component B:

<template>
  <select v-model="selectedOption" @change="onChange">
    <option v-for="item of props.list" />{{item}</option>
  </select>
</template>

<script lang="ts">
import { defineComponent, ref, PropType } from 'vue';
export default defineComponent({
  emits: ['update'],
  props: {
    selected: {
      type: String
    },
    list: {
      type: Object as PropType<String[]>
    }
  }
  setup: (props, { emit }) => {
    const selectedOption = ref(props.selected); // this doesn't work when loading is finished and props are updated
    
    const onChange = () => emit('update', selectedOption.value);

    return { selectedOption, onChange, props }
  }
});
</script>

Thank you.

CodePudding user response:

In component B, you shouldn't need to add both a v-model and a @change.

 <select :value="selected" @change="onChange">
   <option v-for="item in list">{{ item }}</option>
 </select>

Then in the onChange get the value from the select and emit up:

const onChange = (event) => emit('update', event.target.value);

CodePudding user response:

In ComponentB.vue, selectedOption's ref is only initialized to the value of props.selected, but that itself is not reactive (i.e., the ref isn't going to track props.selected automatically):

const selectedOption = ref(props.selected); // not reactive to props.selected

You can resolve this with a watchEffect that copies any new values to selectedOption (which happens automatically whenever props.selected changes):

watchEffect(() => selectedOption.value = props.selected);

Side note: setup() doesn't need to explicitly return props because they're already available to the template by name. Your template could be changed to:

<!-- <option v-for="item of props.list">{{ item }}</option> -->

                                   
  • Related