Sign in
Log inSign up
How to easily sync with multiple v-models in Vue 3 using Composition API

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

Thomas Findlay's photo
Thomas Findlay
·Mar 17, 2021·

4 min read

This article was originally published at The Road To Enterprise.


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.

Alt Text

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 and subscribe to the newsletter.

Hassle-free blogging platform that developers and teams love.
  • Docs by Hashnode
    New
  • Blogs
  • AI Markdown Editor
  • GraphQL APIs
  • Open source Starter-kit

© Hashnode 2024 — LinearBytes Inc.

Privacy PolicyTermsCode of Conduct