Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Chron's avatar
Level 6

[Composition API] Component v-model expecting Array but string given

I'm using Vue 3.3.4

I want to make a re-usable select component

<!--Parent.vue-->
<template>
	<Child v-model="dataForm.options" multiple>
		<option v-for="opt in choices" :value="opt.title" :key="opt.id">{{ opt.title }}</option>
	</Child>
</template>
<script>
import { ref } from "vue";
import Child from './Child.vue';
export default {
	components: { Child },
	setup() {
		const dataForm = ref({
			options: []
		});
		return { dataForm }
	}
}
</script>
<!--Child.vue-->
<template>
	<select v-bind="$attrs" :value="modelValue" @change="$emit('update:modelValue', $event.target.value)">
		<slot/>
	</select>
</template>
<script>
import { ref } from "vue";
export default {
	props: {
		modelValue: null,
        //...
	},
	setup(props) {
       //...
	}
}
</script>

Whenever I choose multiple options it only passes 1 value. What could be the solution for this? I know I could just update the Vue version and use the updated syntax but I want to know what could be the solution for this

0 likes
4 replies
dualklip's avatar

Hi @chron

Hi think the problem could be on the emit. Can you try something like this?

Parent Component

<template>
  <ChildComponent v-model="dataForm.options"></ChildComponent>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  setup() {
    const dataForm = ref({
      options: []
    });
    return { dataForm };
  }
};
</script>

Child component

<template>
  <select @change="updateValue">
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
  </select>
</template>
<script>
export default {
  props: {
    modelValue: {
      type: Array,
      default: () => []
    }
  },
  emits: ['update:modelValue'],
  methods: {
    updateValue(e) {
      this.$emit('update:modelValue', e.target.value);
    }
  }
};
</script>
Chron's avatar
Level 6

@dualklip I'm using the Composition API syntax so the syntax is a bit different. emit is doing its thing, it just passes incorrect data.

dualklip's avatar

Yes, but the essence is the same

Parent component

<template>
  <ChildComponent v-model="dataForm.options"></ChildComponent>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
    components: {
        ChildComponent
    },
    setup() {
        const dataForm = ref({
            options: []
        });
        return { dataForm };
    }
};
</script>

Child component

<template>
  <select v-model="selectedValue">
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
  </select>
</template>
<script>
import { computed, toRef } from 'vue';
export default {
    props: {
        modelValue: Array
    },
    setup(props, { emit }) {
        const selectedValue = computed({
            get: () => props.modelValue,
            set: (val) => emit('update:modelValue', val)
        });
        return {
            selectedValue
        };
    }
};
</script>
dualklip's avatar

Perhaps you are looking something that build the entire array like this

Parent component

<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
	components: {
		ChildComponent
	},
	setup () {
		const dataForm = ref({
			options: [
				{id: 1, label: 'Option 1', selected: false},
				{id: 2, label: 'Option 2', selected: false},
				{id: 3, label: 'Option 3', selected: false}
			]
		});
		return {dataForm};
	}
};
</script>

<template>
	<ChildComponent :options="dataForm.options"></ChildComponent>
	<div>{{dataForm.options}}</div>
</template>

Child component

<script>
export default {
	props: {
		options: Array
	},
	setup(props) {
		const onSelectChange = (e) => {
			props.options.forEach((option) => {
				option.selected = (option.label === e.target.value);
			});
		};
		return { onSelectChange };
	}
};
</script>

<template>
	<select @change="onSelectChange">
		<option v-for="(option, index) in options" :key="index"
		        :value="option.label" :selected="option.selected">
			{{ option.label }}
		</option>
	</select>
</template>

example

Please or to participate in this conversation.