# Vue.js Patterns: Using Vue.js 3 Composition API for Reactive Parent to Child Communication — Liran Tal

Vue.js revolves around a reactivity system, which is unlike React. In this article, we will explore how to use the Vue.js 3 Composition API to create a reactive parent to child communication.

Canonical: https://lirantal.com/blog/vuejs-patterns-using-vuejs-3-composition-api-for-reactive-parent-to-child-communication

Here’s the use-case: A parent Vue.js component needs to pass data to a child component. It does so using `Props`. The child component needs to receive this external data as its initial state, but also be able to mutate it internally, e.g: binding two-way data for a text input element.

The limitation we face while implementing that are as follows:

*   A `prop` is designed to be immutable. The child component shouldn’t mutate the data it receives from the parent component.
*   `Ref`s are mutable and allow you to set initial data such as `const content = ref(props.content)`, but depending how you use them, could end up as a pitfall.

## What is the issue with using Vue.js’s `Ref`s?

Refs are part of the Vue.js reactivity system. They are used to create reactive data and they are a proper building block of the Vue.js 3 Composition API.

However, the way you might be using them, per most of the guides I’ve seen, is not going to be helpful with defined use-case.

Consider the following code of a parent component:

```
<template>
  <child-component :content="content"></child-component>
</template>
```

In the child component, you might be looking at using refs by binding them to an initial value that originates from the parent component’s props like so:

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

defineProps({
  content: {
    type: String,
    default: ''
  }
});

const content = ref(props.content);
</script>
```

This is a valid code and will technically work but you’d have to be aware of Vue.js’s lifecycle hooks to understand why it’s not the best approach.

In this component `setup` convention, the `ref()` call would only be ever called once - when the component is mounted. This means that if the parent component changes the `content` prop, the child component will not be aware of that change.

## How to use `Ref`s properly? `watch()` to the rescue!

The solution to this problem is to use the `watch()` function, or its smarter sibling: `watchEffect()`. Like Refs, the watch functions are a part of the Vue.js reactivity system and allow you to watch for changes in a reactive object changes, regardless of the component’s lifecycle hooks.

Following is a complete code example.

We’ll show how to to pass default, initial values, from a parent component to a child component and set these as values using Vuetify 3 and Vue Composition API, while also enabling two-way binding.

The parent component is defined as follows, with a `content` prop that is passed to the child component and may be dynamically changed from the parent component based on its own state:

```
<template>
  <child-component :content="content"></child-component>
</template>
```

In the child component, we’ll define a text input element that will be bound to a content ref with Vue.js’s own `v-model` directive. This will allow us to mutate the value of the `content` ref from the child component, which achieves the two-way binding we’re looking for.

Also allowing the parent component to change the value of the `content` prop, which will be reflected in the child component.

```
<template>
    <v-text-field v-model="content" />
</template>

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

defineProps({
  content: {
    type: String,
    default: ''
  }
});

const content = ref(props.content);
</script>
```

To allow the child component to be aware of changes in the props passed to it we’ll use the `watchEffect()` function to watch for changes in the `content` prop. This will allow us to update the value of the `content` ref, which will be reflected in the child component.

Add the following code to the child component in the global scope next to the `content` variable definition:

```
<script setup>
import { watch } from 'vue';

watch(
  () => props.content,
  () => { content.value = props.content }
);
</script>
```

Let’s explain what is going on here with the `watch()` function.

The `watch` function you see here is part of Vue’s Composition API. Its purpose is to observe and react to changes on a specific reactive source. It could be a simple reactive reference (like a `ref`), or it could be a function that returns a reactive source.

Here’s how it works:

*   `() => props.content`: This is called the “source” function. It should return the value that you want to watch. In this case, it’s the `content` value passed from the props.

*   `() => { content.value = props.content }`: This is the callback function that gets executed when the source changes. In this case, it assigned the `props.content` value to the `content` ref.


You may also pass an additional 3rd argument options object such as `{ immediate: true }` which means the callback should be run immediately after the `watch` gets created, even before the source has changed. This is useful for cases where you want the callback to run initially when setting up the `watch`.

We can instead use the `watchEffect()` function is a less verbose way to achieve the same result. The `watchEffect()` function will be called every time the `content` prop hydrates, and will update the value of the `content` ref, which will be reflected in the child component.

```
<script setup>
import { watchEffect } from 'vue';

 watchEffect(() => {
  content.value = props.content;
});
</script>
```

### Bonus: using this pattern without the `setup` syntactic sugar

Here’s a generic re-write to the above, but without using the `setup` script convention, demonstrating how our use-case can be achieved using Vue 3’s Composition API:

```
import { ref, watchEffect } from 'vue';

export default {
  props: {
    initialData: {
      type: Array, // change this based on your data type
      default: () => ([]), // provide a default value
    },
  },
  setup(props) {
    // Make a local copy of the received prop
    const data = ref([...props.initialData]);

    // Update local data whenever the prop changes
    watchEffect(() => {
      data.value = [...props.initialData];
    });

    // Now you can manipulate `data` within your component

    return {
      data
    };
  }
};
```

In this example, `initialData` is a prop passed from the parent component. The `data` reactive variable is a local copy of `initialData`. The `watchEffect` function is used to update `data` whenever `initialData` changes, ensuring `data` always starts with the current value of `initialData`.

Since `data` is a local copy, it can be freely mutated inside the child component without affecting the prop’s value in the parent component.

## More writing

[All writing →](/blog)

*   Sep 5, 2026

    ### [Self-Hosting Hermes Agent with Matrix E2EE on Proxmox](/blog/self-host-hermes-agent-matrix-e2ee-proxmox)

*   Aug 2, 2026

    ### [The Practical Second Brain: Building an LLM-Operated Markdown Knowledge Graph](/blog/practical-second-brain-llm-markdown-knowledge-graph)

*   Aug 1, 2026

    ### [Building a Second Brain with LLMs and AI Agents](/blog/building-second-brain-llms-ai-agents)


* * *

Liran Tal is the author of the book [Node.js Secure Coding: Defending Against Command Injection Vulnerabilities](https://www.nodejs-security.com/)

[Start Building Secure Node.js Applications ↗](https://www.nodejs-security.com/)

 [![](/_astro/nodejs-secure-coding-book-3d-style.DiTWpzFs_Z3c7s1.png)](https://www.nodejs-security.com/)
