·
2 min read

Vue Tip: Declare and Mutate v-model Props as Normal Variable Using defineModel

Vue Tip: Declare and Mutate v-model Props as Normal Variable Using defineModel Image

Info

This experimental feature will be available in Vue 3.3. If you want to try it out now in Vue 2 or Vue 3, you can use it with the Vue Macros library.

defineModel is a compiler macro that allows you to declare and mutate v-model props as the same as a normal variable.

Example without defineModel

Let's take a look at a simple example that uses v-model. We have a Parent.vue component that passes a counter ref to a Child.vue component:

Parent.vue
<script setup lang="ts"> import { ref } from 'vue' import Child from './components/Child.vue' const state = ref({ count: 0 }) </script> <template> <div> <span>Parent state: {{ state }}</span> <Child v-model="state.count" /> </div> </template>

Let's take a look at the implementation of the child component:

Child.vue
<script setup lang="ts"> import { ref } from 'vue' const props = defineProps<{ modelValue: number }>() const emit = defineEmits<{ (e: 'update:modelValue', value: number): void }>() const onClick = () => { props.modelValue += 1 emit('update:modelValue', props.modelValue) } </script> <template> <div> <span>Child state: {{ modelValue }}</span> <button @click="onClick">Increment child state</button> </div> </template>

As Child.vue receives the v-model you need to declare a prop called modelValue which receives the v-model value. Additionally, you need to declare an emit called update:modelValue that is used to update the parent that the modelValue has been updated.

As props are readonly and you should not mutate them, you cannot update modelValue and will receive the following warning:

Vue warn

Set operation on key "modelValue" failed: target is readonly.

I wrote a tip about this topic and how you can solve this warning. defineModel provides a nice solution to this problem, so let's take a look at it.

Example with defineModel

Info

In the following example, I'm using Vue Macros's defineModels which behaves identically to defineModel available since Vue 3.3.0-alpha.9

We can simplify our child component by using defineModels:

Child.vue
<script setup lang="ts"> const { modelValue, count } = defineModels<{ modelValue: number }>() const onClick = () => { modelValue.value += 1 } </script> <template> <div class="container"> <span>Child with defineModels state: {{ modelValue }}</span> <button @click="onClick">Increment child state</button> </div> </template>

The defineModels compiler macro will declare a prop with the same name and a corresponding update:propName event when it is compiled.

By updating the modelValue ref, the corresponding update:propName event is automatically emitted.

Try it yourself in the following StackBlitz project: