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> -->