Parent-child relation in Vue.js

In Vue.js, you have a One-Way Data Flow. We explore this on a couple of examples.

The simple input field

If you have an input field, you would usually need to pass an value and listen for input change to follow the One-Way Data Flow:

<input
  :value="text"
  @input="event => text = event.target.value">

To avoid overcomplicated code, there is v-model

<input v-model="text">

Component v-model

Now assume we have a custom component and we bind model value to it according to One-Way Data Flow:

<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

The component has its own input field like this:

<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

For this case, we can bind searchText like this on the CustomComponent:

<CustomInput v-model="searchText" />

Notice that we can't use

<input v-model="modelValue">

within CustomComponent, because modelValue is a prop that should not be changed. However, we can use writable computed property, and then we get this cleaner code:

<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

defineProps(['modelValue'])
defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
    <input v-model="value" />
</template>

Component nested v-model

Assume we pass an object.

<script setup>
const user = ref({name: 'Max', age: '18'});
</script>

<template>
<CustomInput v-model="user" />
</template>

And inside CustomInput we have two inputs that wants to access name and age. We could do it like this:

<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);

const user = computed({
    get() {
        return props.modelValue
    },
    set(value){
        emit('update:modelValue', value)
    }
})
</script>

<template>
  <input v-model="user.name" />
  <input v-model="user.age" />
</template>