Logo
Published on

Vue 3: Accessing Child Components Data From Parent Components?

Authors
  • Name
    Twitter

I will explain the differences in data sharing between option and composition apis.Also, demonstrate potential ways to use the composition API to share data from a child component to parent component, such as emit and expose.

If you create a child component using composition API, properties and methods of this component will be private.You can’t access private properties and methods from outside of that component.

<script setup>//composition api with setup script  
// any property or methods are private   
import { ref } from 'vue'  
const name= ref("child component");  
const increase =()=>{  
  return count.value = count.value + 1;  
} </script>

But,if you create a component using option API, this child components properties and methods will be public. So, parent component can access this child components properties and methods using that component instance or $parent chains.

<script>  
// any property or method are public  
//accessible from parent   
export default {  
    data() {  
        return {  
            name: '',  
        }  
    },  
    methods: {  
         increase() {  
         return this.count = this.count + 1;  
        },     
    },  
      
}  
</script>

There are two ways to share data from child to parent component using composition API with script setup.

  1. defineExpose
  2. Emit Event

defineExpose: defineExpose method allows the component to expose its methods and properties to be publically accessible for the parent component.

To achieve data passing between child component to parent component using composition API with defineExpose, you need to follow two steps.

Step 1:use the defineExpose method in your child component to expose properties and methods..

defineExpose()

I’ve made the name, isAdult, and count properties public, along with increase and decrease methods. Except for age property, the rest of them are publically accessible from the parent component.

//ChildComponent.vue  
  
<script setup>  
import { ref, defineExpose } from "vue";  
  
const name = ref("child component");  
const count = ref(1);  
const isAdult = ref(false);  
const age = ref(17);  
  
  
const increase = () => {  
  count.value = count.value + 1;  
  isAdult.value = count.value > age.value;  
};  
  
const decrease = () => {  
  if (count.value > 0) count.value = count.value - 1;  
  isAdult.value = count.value > age.value;  
};  
  
//age is private  
//rest of the properties & methods are public  
defineExpose({  
  name,  
  count,  
  isAdult,  
  increase,  
  decrease,  
});  
</script>  
  
<template>  
  <h5>Child To Parent Data passing</h5>  
</template>

Step 2: In your parent component, you need to add your child component. Now, you need to create an instance of the child component.

Using Template Ref **,**you can create an instance of the child component. I have explained Template Ref in my previous post. Click to go there.

Here, I have added ChildComponent.vue in the parent component. I have also created an instance named child from ChildComponent.vue. Using that instance, I have accessed publically available name,isAdult, and count properties, along with increase and decrease methods.

//ParentComponent.vue  
  
<script setup>  
import ChildComponent from "./ChildComponent.vue";  
import { ref, onMounted, computed } from "vue";  
  
const child = ref(null);  
const childComponetName = ref("");  
  
const onChildIncrement = () => {  
  child.value.increase();  
  //increase child count  
};  
  
const onChildDecrement = () => {  
  child.value.decrease();  
  //decrease child count  
};  
  
onMounted(() => {  
  childComponetName.value = child.value.name;  
  // child components name is not updating  
  //so i have read it after initial render  
});  
  
const childComponetCount = computed(() => {  
  return child.value?.count;  
  // if child count update it will return the latest count;  
});  
const isParentsChildAdult = computed(() => {  
  return child.value?.isAdult;  
  // if child isAdult update it will return the latest isAdult;  
});  
</script>  
  
<template>  
  <div>  
    <ChildComponent ref="child" />  
      
    <p>Count:{{ childComponetCount }}</p>  
    <p>Adult child? {{ isParentsChildAdult }}</p>  
  
    <div class="btn">  
      <button type="button" @click="onChildDecrement">decrement</button>  
      <button type="button" @click="onChildIncrement">increment</button>  
    </div>  
  </div>  
</template>  
  
<style scoped>  
.btn {  
  display: flex;  
  justify-content: center;  
  gap: 20px;  
}  
</style>

Emit Event: Using the emit method, you can create a custom event and pass data from the child to the parent component/listening component. Whenever a child component triggers an event , listening/parent component will listen to that event and act accordingly.

Emit methods accept a unique event name and event data. You can ignore event data, But the event name is mandatory.

emit('customEventName', eventData);  
         or  
emit('customEventName');  
//customEventName must be unique

To achieve data passing between child component to parent component using composition API with emit , you need to follow two steps.

Step 1: Create a custom event with emit in your child component**.** You can create custom events from your component template or script section.

Script Section:If you use emit with script setup, you must declare the custom events directly in defineEmits.Here, I have declared both handleIncrease & handleDecrease events in defineEmits. Later, I created those events and passed properties as event data.

<script setup>  
import { defineEmits, ref } from "vue";  
  
//list of declared custom events  
const emit = defineEmits(["handleIncrease", "handleDecrease"]);  
  
const count = ref(1);  
const age = ref(17);  
  
  
const increase = () => {  
  count.value = count.value + 1;  
  const isAdult = count.value > age.value;  
  emit("handleIncrease", count.value, isAdult);  
 //custom event with event data  
};  
//  
const decrease = () => {  
  if (count.value > 0) count.value = count.value - 1;  
  const isAdult = count.value > age.value;  
  emit("handleDecrease", count.value, isAdult);  
//custom event with event data  
};  
</script>

Template Section: For Inline emit, you don’t need to declare any custom events. That’s why I have created a directly inline getSearchInput event from templates and passed search value as event data.

<script setup>  
import {ref } from "vue";  
const search = ref("");  
</script>  
  
<template>  
  <input  
      type="text"  
      class="form-control"  
      v-model="search"  
      @input="$emit('getSearchInput', search)" />  
    
</template>  
  
<style scoped>  
.form-control {  
  margin: 10px;  
}  
  
</style>

Considering Both Cases Complete ChildComponent Code Using Emit :

//ChildComponent.vue  
<script setup>  
import { defineEmits, ref } from "vue";  
  
//list of declared custom events  
const emit = defineEmits(["handleIncrease", "handleDecrease"]);  
  
const count = ref(1);  
const age = ref(17);  
const search = ref("");  
  
const increase = () => {  
  count.value = count.value + 1;  
  const isAdult = count.value > age.value;  
  emit("handleIncrease", count.value, isAdult);  
  //custom event with event data  
};  
//  
const decrease = () => {  
  if (count.value > 0) count.value = count.value - 1;  
  const isAdult = count.value > age.value;  
  emit("handleDecrease", count.value, isAdult);  
  //custom event with event data  
};  
</script>  
  
<template>  
  <div class="btn">  
    <button type="button" @click="decrease">decrement</button>  
    <button type="button" @click="increase">increment</button>  
  </div>  
  
  <!-- Inline emit getSearchInput & searchValue is passing as argument-->  
  <input  
    type="text"  
    class="form-control"  
    v-model="search"  
    @input="$emit('getSearchInput',search)"  
  />  
</template>  
  
<style scoped>  
.form-control {  
  margin: 10px;  
}  
.btn {  
  display: flex;  
  justify-content: center;  
  gap: 20px;  
}  
</style>

Step 2: In your parent /listening component, you need to add a child component. Now you have to bind those custom events in Vue.js templates using the on directive or its shorthand @ symbol.

So, I have added ChildComponent.vue in ParentComponent.vue and binding all three custom events.


//ParentComponent.vue  
<script setup>  
  
import ChildComponent from "./ChildComponent.vue";  
import { ref } from "vue";  
const parentCount = ref(1);  
const isParentsChildAdult = ref(false);  
  
const increaseParent = (childCount, isAdult) => {  
  parentCount.value = childCount;  
  isParentsChildAdult.value = isAdult;  
 //Whenever in child component user click increase method,  
 //handleIncrease event will listen and act accordingly  
};  
  
const decreaseParent = (childCount, isAdult) => {  
  parentCount.value = childCount;  
  isParentsChildAdult.value = isAdult;  
 //Whenever in child component user click descrease method,  
 //handleDecrease event will listen and act accordingly  
  
};  
  
const showAlert = (value) => {  
  alert(value);  
 //Whenever in child component user fills input ,  
 //getSearchInput event  will listen   
   
};  
</script>  
  
<template>  
  <p>{{ parentCount }}</p>  
  <p>Adult child? {{ isParentsChildAdult }}</p>  
  <ChildComponent  
    @handleIncrease="increaseParent"  
    @handleDecrease="decreaseParent"  
    @getSearchInput="showAlert" />  
</template>

That's it for this topic. Thank you for reading!