DEV Community

loading...
Cover image for 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

thomasfindlay profile image Thomas Findlay ・3 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)

Forem Open with the Forem app