How to easily sync with multiple v-models in Vue 3 using Composition API

By Thomas Findlay 16 Mar, 2021

Vue 3 has brought many new features, and the ability to use more than one v-model directive on the same element is one of them. I want to share with you a quick tip on how to handle updating the state of a parent component when using multiple v-models. It's especially useful when dealing with forms. You can find the full code example in this GitHub repository.

For this example, we will use a form shown in the image below.

Form

Below you can find the code for it. We have two files - App.vue, which has the form state and renders the Form component. The Form.vue component renders the form element with labels and inputs fields.

App.vue

<template>
  <div :class="$style.container">
    <Form
      v-model:name="form.name"
      v-model:surname="form.surname"
      @submit="onSubmit"
    />
  </div>
</template>

<script>
import { ref } from 'vue'
import Form from './components/Form.vue'

export default {
  components: {
    Form,
  },
  setup() {
    const form = ref({
      name: '',
      surname: '',
    })

    const onSubmit = () => console.log(form)

    return {
      form,
      onSubmit,
    }
  },
}
</script>

<style module>
.container {
  max-width: 30rem;
  @apply mx-auto py-8;
}
</style>

components/Form.vue

<template>
  <form @submit.prevent="$emit('submit')">
    <div :class="$style.formBlock">
      <label :class="$style.label">Name</label>
      <input
        v-model="nameState"
        :class="$style.input"
        type="text"
        aria-label="Name input"
      />
    </div>
    <div :class="$style.formBlock">
      <label :class="$style.label">Surname</label>
      <input
        v-model="surnameState"
        :class="$style.input"
        type="text"
        aria-label="Surname input"
      />
    </div>
    <div>
      <button
        class="float-right bg-blue-100 text-blue-900 px-4 py-3 rounded font-semibold"
        type="submit"
      >
        Submit
      </button>
    </div>
  </form>
</template>

<script>
import { useVModel } from '../composables/useVModel.js'
export default {
  emits: ['update:name', 'update:surname', 'submit'],
  props: {
    name: String,
    surname: String,
  },
  setup(props) {
    return {
      nameState: useVModel(props, 'name'),
      surnameState: useVModel(props, 'surname'),
    }
  },
}
</script>

<style module>
.formBlock {
  @apply flex flex-col mb-4;
}
.label {
  @apply mb-2;
}
.input {
  @apply px-4 py-3 shadow rounded border border-gray-300 bg-white;
}
</style>

To update the state in the parent, we need to emit an update:<modelValue> event.

Here is the code for the useVModel helper.

composables/useVModel.js

import { computed, getCurrentInstance } from 'vue'

export const useVModel = (props, propName) => {
  const vm = getCurrentInstance().proxy

  return computed({
    get() {
      return props[propName]
    },
    set(value) {
      vm.$emit(`update:${propName}`, value)
    },
  })
}

We have to pass the props object to keep the reactivity intact and the prop name which we want to sync with. Inside of the useVModel we get access to the current instance via getCurrentInstance(), as we need access to the $emit method. The computed receives an object with a getter and setter. The getter returns the value passed via props, whilst the setter emits an event to update the value. Thanks to this little helper, keeping the state passed through props via v-models is much cleaner and simpler.

I hope you enjoyed this article. If you would like to learn more tips, advanced patterns, techniques and best practices related to Vue, you might want to check out "Vue - The Road To Enterprise" book.


Want to learn something new?

Subscribe to the newsletter!


Thomas Findlay photo

About author

Thomas Findlay is a 5 star rated mentor, full-stack developer, consultant, and technical writer. He works with many different technologies such as JavaScript, Vue, Nuxt, React, Next, React Native, Node.js, Python, PHP, and more. He has obtained MSc in Advanced Computer Science degree with Distinction at Exeter University, as well as First-Class BSc in Web Design & Development at Northumbria University.

Over the years, Thomas has worked with many developers and teams from beginners to advanced and helped them build and scale their applications and products. He also mentored a lot of developers and students, and helped them progress in their careers.

To get to know more about Thomas you can check out his Codementor and Twitter profiles.