I have a component (a <select>
menu) which I need to render more than once.
I set it up to receive a prop to determine which of its options is selected in each case (the first menu should have Perth selected and the second one Melbourne):
<template>
<div>
<SelectMenu :selectedItem="menuASelectedItem" :options="options" />
<SelectMenu :selectedItem="menuBSelectedItem" :options="options" />
</div>
</template>
<script>
import SelectMenu from './SelectMenu.vue';
export default {
components: {
SelectMenu
},
data() {
return {
menuASelectedItem: 3,
menuBSelectedItem: 2,
options: [
{ value: '1', label: 'Sydney' },
{ value: '2', label: 'Melbourne' },
{ value: '3', label: 'Perth' },
],
};
},
};
</script>
and I assumed I could just use that value in the component:
<template>
<select v-model="SelectedItem">
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
selectedItem: {
type: String,
required: true,
},
options: {
type: Array,
required: true,
},
},
};
</script>
But this doesn't work. Or generate an error. It just doesn't select anything in the menus. By Googling I have figured out that I can do this by adjusting the component so that the prop becomes a local variable:
<template>
<select v-model="localSelectedItem"> <!-- localSelectedItem, not SelectedItem -->
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
selectedItem: {
type: String,
required: true,
},
options: {
type: Array,
required: true,
},
},
data() { // data block handles turning selectedItem into localSelectedItem
return {
localSelectedItem: this.selectedItem,
};
},
};
</script>
So, I've got my code working, but I need to understand why it didn't work the first, more obvious way. Isn't that exactly what props are for? The prop is a variable sent into the component from outside. Why can't it be used directly?
You shouldn't mutate the props. In this case, v-model
enables two-way binding and mutates the prop. To avoid this, you could bind it to a writable computed property that retrieves the prop value. When it is set, emit the value to the parent:
<template>
<select v-model="localeSelectedItem">
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
selectedItem: {
type: String,
required: true,
},
options: {
type: Array,
required: true,
},
},
emits: ["update:selectedItem"],
computed: {
localeSelectedItem: {
get() {
return this.selectedItem;
},
set(value) {
this.$emit("update:selectedItem", value);
},
},
},
};
</script>
and in the parent bind it as follows :
<template>
<div>
<SelectMenu v-model:selectedItem="menuASelectedItem" :options="options" />
<SelectMenu v-model:selectedItem="menuBSelectedItem" :options="options" />
</div>
</template>
You could learn more about this feature in component v-model