Vue defineModel Usage

Learn how to use defineModel in Vue 3 for 2-way data binding.

I recently needed to break a large form into smaller components in a Vue 3 application. I wanted to use defineModel for 2-way data binding between the parent and child components. Here's how I did it.

The parent component needs to have all the data that it will be submitting to the server. It passes chunks of that data to child components using the v-model attribute.

Child components create a model variable using defineModel macro to create a reactive property that will be bound to the parent component's data. This allows for 2-way data binding, meaning that changes in the child component will update the parent's data and vice versa.

Here's a simplified example of breaking a form into two child components, each handling different parts of the form data.

ParentComponent.vue
<script setup>
  import { ref } from 'vue'
  import ChildComponent1 from './ChildComponent1.vue'
  import ChildComponent2 from './ChildComponent2.vue'

  const childData1 = ref({
    name: '',
    email: '',
  })

  const childData2 = ref({
    contacts: [],
  })

  const submitForm = () => {
    // Handle form submission logic here
    console.log('Form submitted with:', {
      childData1: childData1.value,
      childData2: childData2.value,
    })
    const formData = {
      ...childData1.value,
      ...childData2.value,
    }
    // You can now send formData to your server
  }
</script>

<template>
  <div>
    <form @submit.prevent="submitForm">
      <ChildComponent1 v-model="childData1" />
      <ChildComponent2 v-model="childData2" />
      <button type="submit">Submit</button>
    </form>
  </div>
</template>
ChildComponent1.vue
<script setup>
  const model = defineModel()
</script>

<template>
  <div>
    <input v-model="model.name" type="text" placeholder="Name" />
    <input v-model="model.email" type="email" placeholder="Email" />
  </div>
</template>
ChildComponent2.vue
<script setup>
  import { ref } from 'vue'

  const model = defineModel()

  const newContact = ref('')
  const addContact = () => {
    model.value.contacts.push(newContact.value)
  }
</script>

<template>
  <div>
    <ul class="mb-2">
      <li v-for="(contact, idx) in model.contacts" :key="idx">{{ contact }}</li>
    </ul>

    <label for="contacts-input">New Contact</label>
    <input
      id="contacts-input"
      v-model="newContact"
      type="text"
      placeholder="Contacts"
    />
    <button @click="addContact">Add Contact</button>
  </div>
</template>