Passing Reactive Variables from Child to Parent in Vue 3 Composition API
The Vue 3 Composition API offers powerful tools for managing data and logic within your components. However, one common challenge arises when needing to communicate reactive data between parent and child components. This article explores how to efficiently pass a reactive variable from a child component to its parent using the Composition API.
The Scenario: Child Component Needs to Update Parent Data
Let's imagine a scenario where a child component, "ProductDetails," displays information about a specific product. This child component also has a button that allows users to add the product to their cart. When the button is clicked, we want to update the "cartItems" array in the parent component, "ShoppingCart."
Original Code (Before Reactivity)
Parent Component (ShoppingCart.vue)
<template>
<div>
<ProductDetails :product="selectedProduct" @addToCart="handleAddToCart"></ProductDetails>
<ul>
<li v-for="(item, index) in cartItems" :key="index">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
import ProductDetails from './ProductDetails.vue';
export default {
components: {
ProductDetails,
},
data() {
return {
selectedProduct: {},
cartItems: [],
};
},
methods: {
handleAddToCart(product) {
this.cartItems.push(product);
},
},
};
</script>
Child Component (ProductDetails.vue)
<template>
<div>
<h2>{{ product.name }}</h2>
<button @click="addToCart">Add to Cart</button>
</div>
</template>
<script>
export default {
props: {
product: {
type: Object,
required: true,
},
},
methods: {
addToCart() {
this.$emit('addToCart', this.product);
},
},
};
</script>
The Problem: In this initial setup, while the parent component successfully receives the product object from the child, the "cartItems" array is not reactive. This means that changes to the array in the parent component will not trigger updates in the child component's view.
The Solution: Leveraging Ref and Emit
To ensure reactive data flow between the parent and child, we'll use the ref
and emit
features of the Composition API:
Parent Component (ShoppingCart.vue)
<template>
<div>
<ProductDetails :product="selectedProduct" :cartItems="cartItems" @addToCart="handleAddToCart"></ProductDetails>
<ul>
<li v-for="(item, index) in cartItems.value" :key="index">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
import { ref } from 'vue';
import ProductDetails from './ProductDetails.vue';
export default {
components: {
ProductDetails,
},
setup() {
const cartItems = ref([]);
const handleAddToCart = (product) => {
cartItems.value.push(product);
};
return {
selectedProduct: {},
cartItems,
handleAddToCart,
};
},
};
</script>
Child Component (ProductDetails.vue)
<template>
<div>
<h2>{{ product.name }}</h2>
<button @click="addToCart">Add to Cart</button>
</div>
</template>
<script>
export default {
props: {
product: {
type: Object,
required: true,
},
cartItems: {
type: Object,
required: true,
},
},
methods: {
addToCart() {
this.$emit('addToCart', this.product);
},
},
};
</script>
Explanation:
-
Reactive Array: We define the "cartItems" array as a
ref
in the parent component. This makes it reactive and ensures that updates to the array trigger view updates. -
Passing the Ref: The parent component now passes the "cartItems" ref as a prop to the child component.
-
Emitting the Product: The child component still emits the "addToCart" event with the product object.
-
Updating the Array: In the parent component's "handleAddToCart" method, we directly push the received product into the "cartItems.value" array. This updates the reactive array, leading to view updates in both parent and child components.
Why This Works: Reactive Data Flow
By using a ref and prop-passing mechanism, we create a seamless reactive data flow:
-
Parent to Child: The parent component passes the "cartItems" ref to the child, making the child aware of the parent's reactive data.
-
Child to Parent: The child component emits the "addToCart" event, sending the product object to the parent component.
-
Parent to View: The parent component updates the "cartItems.value" array based on the emitted product, triggering view updates due to the reactive nature of the ref.
Conclusion
This approach effectively solves the challenge of passing reactive data from child to parent components in Vue 3. By leveraging the Composition API's "ref" and "emit" features, you can establish a smooth and reactive communication between your components.