# V-Model in Vue3

By using Vue's new Composition API, it feels like Vue's reactivity system has been freed of the constraints of the component. This article tells you that how to work with components and v-model more easily.

# Recap: The v-model directive

If you've worked with Vue, you know the v-model directive:

<input type="text" v-model="localStateProperty">

It's a great shortcut to save us from typing template markup like this:

<input 
  type="text" 
  :value="localStateProperty" 
  @change="localStateProperty = $event.target.value"
>

The great thing is that we can also use it on components:

<message-editor 
  :modelValue="message" 
  @update:modelValue="message = $event"
>

However, to implement this contract of a prop and an event, the component would have to look something like this:

<template> 
  <label> 
    <input type="text" :value="modelValue", @input="(event) => $emit('update:modelValue', event.target.value)" > 
  <label>
</template>

<script> 
export default { 
  props: { 
    'modelValue': String, 
  } 
}
</script>

This seems pretty verbose, however.

We have to do this because we cannot directly write to the prop. We have to emit the correct event and leave it to the parent to decide how to deal with the update we communicate, because it’s the parent’s data, not that of our <message-editor> component. So we can’t use v-model on the input in this case.

There are patterns of how to deal with this in the Options API that you may know from Vue 2, but today I want to look at how we can solve this challenge in a neat way with the tools offered by the Composition API.

# The challenge: Reduce boilerplate

What we want to achieve is an abstraction that lets us use the same v-model shortcut on the input, even though we don’t actually want to write to our local state and instead want to emit the correct event. This is what we want the template to look like when we’re done:

<template> 
  <label> 
    <input 
      type="text" 
      v-model="message" 
    /> 
  <label>
</template>

So let’s implement this with the composition API:

import { computed } from 'vue'
export default { 
  props: { 
    'modelValue': String, 
  },
  setup(props, { emit }) { 
    const message = computed({ 
      get: () => props.modelValue, 
      set: (value) => emit('update:modelValue', value) 
    }) 

    return { 
      message,
    } 
  }
}

In setup, we create such a computed property, but a special one: our computed prop has a getter and setter, so we can actually read its derived value as well as assign it a new value.

This is how our computed prop behaves when used in Javascript:

message.value
// => 'This returns a string'
message.value = 'This will be emitted up'
// => calls emit('onUpdate:ModelValue', 'This will be emitted up')

By returning this computed prop from the setup() function, we expose it to our template. And there, we can now use it with v-model to have a nice clean template:

<template> 
  <label> 
    <input 
      type="text" 
      v-model="message" 
    > 
  <label>
</template>

<script>
import { computed } from 'vue'
export default { 
  props: { 
    'modelValue': String, 
  },
  setup(props, { emit }) { 
    const message = computed({ 
      get: () => props.modelValue, 
      set: (value) => emit('update:modelValue', value) 
    }) 

    return { 
      message, 
    } 
  }
}
</script>

Now the template is pretty clean. But at the same time, we had to write a bunch of boilerplate in setup() to achieve this. It seems we just moved the boilerplate from the template to the setup function.

So let’s extract this logic into its own function—a composition function—or for short: a “composable”.

# Turn it into a composable

A composable is just a function that we use to abstract some code out from our setup function. Composables are the strength of this new Composition API and a core principle that allows for better code abstraction and composition.

This is what we are going for:

📄 modelWrapper.js

import { computed } from 'vue'

export function useModelWrapper(props, emit) { 
  /* implementation left out for now */
}

📄 MessageEditor.vue

import { useModelWrapper } from '../utils/modelWrapper'

export default { 
  props: { 
    'modelValue': String, 
  } 
  setup(props, { emit }) { 
    return { 
      message: useModelWrapper(props, emit), 
    } 
  }
}

Notice how the code in our setup() function was reduced to a one-liner (if we assume we are working in a nice editor that can add the import for useModelWrapper automatically for us, like VS Code).

How did we achieve that? Well, essentially all we had to do was copy and paste the code from setup into this new function! This is what it looks like:

import { computed } from 'vue'

export function useModelWrapper(props, emit) { 
  return computed({ 
    get: () => props.modelValue, 
    set: (value) => emit('update:modelValue', value) 
  })
}

Okay, that was straightforward, but can we do better? Why yes, we can!

Though to understand in what way we can, we will make a short detour back to how v-model works on components…

In Vue 3, v-model can take an additional argument to apply it to a prop other than modelValue. This is useful if the component wants to expose more than one prop as a target for v-model: v-model:argument

<message-editor 
  v-model="message" 
  v-model:draft="isDraft" 
/>

The second v-model could be implemented like this:

<input
  type="checkbox" 
  :checked="draft",
  @change="(event) => $emit('update:modelValue', event.target.checked)"
 >

So verbose template code again. Time to adjust our new composition function so that we can reuse it for this case as well.

To achieve that, we want to add a second argument to the function that specifies the name of the prop that we actually want to wrap. Since modelValue is the default prop name for v-model, we can use it as the default for our wrapper’s second argument as well:

📄 modelWrapper.js

import { computed } from 'vue'
export function useModelWrapper(props, emit, name = 'modelValue') { 
  return computed({ 
    get: () => props[name], 
    set: (value) => emit(`update:${name}`, value) 
  })
}

That’s it. Now we can use this wrapper for any v-model prop.

So your final component would look like this:

<template> 
  <label> 
    <input type="text" v-model="message" > 
    <label> <label> 
    <input type="checkbox" v-model="isDraft"> Draft 
  </label>
</template>

<script>
import { useModelWrapper } from '../utils/modelWrapper'

export default { 
  props: { 
    modelValue: String, 
    draft: Boolean
  },
  setup(props, { emit }) { 
    return { 
      message: useModelWrapper(props, emit, 'modelValue'), 
      isDraft: useModelWrapper(props, emit, 'draft') 
    }
  }
}
</script>

# Where to go from here

This composable is not only useful when we want to map a modelValue prop onto an input in our template. We can also use it to pass the computed ref to other composables that expect a ref they can assign a value to.

By first wrapping the modelValue prop like we did above, the second composition function can be unaware of the fact that we actually don’t deal with a piece of local state. We abstracted away that implementation detail in our nice little useModelWrapper composable, and so the other composable can treat it as local state.