Home / Blog / v-model and defineModel: A Comprehensive Guide to Two-Way Binding in Vue.js 3
v-model and defineModel: A Comprehensive Guide to Two-Way Binding in Vue.js 3

v-model and defineModel: A Comprehensive Guide to Two-Way Binding in Vue.js 3

Mostafa Said
Mostafa Said
Updated: August 19th 2024

Are you ready to master two-way binding in Vue js 3 using defineModel? In this comprehensive guide, we'll explore how to implement seamless data flow between components using this powerful composition API Macro. By the end, you'll have a clear understanding of how to use the classic v-model directive and leverage defineModel for efficient two-way binding in your Vue.js applications.

Understanding Two-Way Binding

Two-way binding acts like a real-time bridge between a component's visual elements (the template) and its underlying data. This dynamic duo ensures that whenever a user interacts with the component, like clicking a button or entering text, the data is automatically updated behind the scenes.

And guess what? It works the other way around too! If you programmatically change the data in your Vue.js application, the component's template will instantly reflect the update. This seamless synchronization keeps your application reactive and responsive to user interactions, resulting in a unique user experience.

Implementing Two-Way Binding in Vue.js

Imagine you have a parent component named App.vue with a form where users can enter information. You want any edits they make to be instantly reflected in a variable called inputValue. Likewise, if you update inputValue in your code, the form should show the new value. This effortless back-and-forth exchange is the power of two-way binding! Let’s have a look at the following script setup with composition API example:

<template>
  <div>
    <form action="/">
      <input v-model="inputValue" />
    </form>
  </div>
</template>

<script setup>
    import { ref } from 'vue';

    const inputValue = ref();
</script>

Using v-model with basic input fields is a piece of cake. But what if you want to create custom components with their own data and behaviors? This is where Single-File Components (SFCs) come in! Let's explore how to achieve two-way binding magic with SFCs.

Implementation with v-model

Let's break down two-way binding with a simple example using v-model. Imagine we have a parent component App.vue with a custom component called FormInput. This FormInput component will display a text input field. We want any changes made in the input to be reflected in the parent component's data.

<template>
  <div>
    <FormInput v-model="inputValue" />
  </div>
</template>

<script setup>
  import { ref } from 'vue';
  import FormInput from './components/FormInput.vue';

  const inputValue = ref('');
</script>

We define a variable ref called inputValue using ref to store the input field's value. Inside the FormInput component, things get interesting!

Originally, the FormInput component is just a basic text input:

<template>
  <input type="text" />
</template>

But thanks to the v-model magic on the parent, FormInput receives a special prop named modelValue. This prop holds the current value of inputValue from the parent component. We can access this prop using defineProps:

<template>
  <input type="text" />
</template>

<script setup>
    defineProps(['modelValue']);
</script>

We then bind the modelValue to the input's value attribute to display the current data:

<template>
  <input type="text" :value="modelValue" />
</template>

<script setup>
    defineProps(['modelValue']);
</script>

So far, this is a one-way street, as the parent tells the child what to show. To achieve two-way binding, we need to inform the parent about any changes in the input field. We do this by listening to the input event and emitting an update event to the parent. But there's a twist! We need to emit an update specifically for modelValue.

Here's how we achieve this:

<template>
  <input type="text" @input="updateValue" :value="modelValue" />
</template>

<script setup>
    defineProps(['modelValue']);
    const emit = defineEmits(['update:modelValue']);

    const updateValue = (e) => {
      const value = e.target.value;
      emit('update:modelValue', value);
    };
</script>

We define an updateValue function that captures the new value from the input event and emits an update:modelValue event with the new data. Because v-model on the child is expecting this specific event (update:modelValue), it automatically updates the parent's inputValue with the new value we emitted.

By following these steps, we create a two-way data flow between the parent and child component using v-model. Any changes in the FormInput will be reflected in the parent's data, keeping everything in sync!

Simplifying with defineModel

While v-model is convenient, it can become verbose for custom components with complex data structures. Here's where defineModel steps in, offering a more streamlined approach.

Here's how it looks in action (replacing v-model with defineModel):

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

<script setup>
  const modelValue = defineModel();
</script>

defineModel takes care of both data synchronization and event emission behind the scenes, eliminating the need for manual event handling and keeping your code concise.

Conclusion

While v-model remains useful for basic scenarios, defineModel offers a cleaner and more concise approach for complex data structures in custom components. As you explore building advanced Vue.js applications, defineModel becomes a valuable tool for efficient two-way data binding.

Feel free to have a look at the complete implementation code here

Start learning Vue.js for free

Mostafa Said
Mostafa Said
With over 7 years of e-learning expertise honed at major companies like Vodafone Intelligent Solutions (_VOIS), Mostafa is full-time instructor at Vue School and a full-stack developer in the wild. He built and maintained dozens of apps using Vue, Nuxt, Laravel, Tailwind, and more. He merges modern teaching methods with coding education to empower aspiring developers of all experience levels. When he's not glued to the code editor, hackathons fuel his competitive spirit, with Deepgram and Appwrite grand prizes under his belt.

Comments

Latest Vue School Articles

From Vue.js Options API to Composition API: Is it Worth it?

From Vue.js Options API to Composition API: Is it Worth it?

Explore the technicalities of transitioning from Options API to Composition API in Vue.js. Discover if migrating your app is worth the effort in our detailed guide
Mostafa Said
Mostafa Said
What’s New in Nuxt 4

What’s New in Nuxt 4

Have anxiety about a new major version of Nuxt coming out? Worried about a big migration project? Don’t worry about it, a peaceful and easy upgrade is literally one of the features of Nuxt version 4.
Daniel Kelly
Daniel Kelly

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.