How to notify child component in Vue 3 using defineExpose and defineEmits

Inter-component communication is one of the features that tools like Vue provides. At the simplest level, inter-component communication is about leveraging the affordances of the library to make two components to talk to each other. Whether it's a modal window or a complex form, getting components to communicate effectively is crucial for a seamless user experience. In this article, I'll explain how a parent component can nudge a child component to behave differently in response to a user action or to other changes in application state. For this, we'll consider two approaches: sharing a variable with the parent using defineExpose and notifying the parent about an event using defineEmits.

Imagine you're building a simple e-commerce application with a product list and a shopping cart. When a user clicks the "Add to Cart" button, you want to open a modal window to confirm the addition. The modal window is a child component, and the product list is the parent component. How do you get the child component to open when the button is clicked? By the end of this article, you'll learn how to use defineExpose and defineEmits to facilitate communication between components.

We'll cover two main approaches to achieve this communication. First, we'll look at how to share a variable with the parent using defineExpose. This method allows the parent component to access the child component's properties and functions directly. Second, we'll discuss how to notify the parent about an event using defineEmits. This approach enables the child component to emit custom events that the parent component can listen to and respond accordingly. By understanding these two techniques, you'll be able to design more robust and maintainable component interactions in your Vue applications.

Sharing a Variable with the Parent

The defineExpose method in Vue 3 allows a child component to expose its properties and functions to the parent component. This way, the parent can access and manipulate the child's state directly, like so:

// ChildComponent.vue
<template>
  <div v-if="isOpen">Modal is open!</div>
</template>

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

const isOpen = ref(false);

defineExpose({ isOpen });
</script>
// ParentComponent.vue
<template>
  <button @click="openModal">Open Modal</button>
  <ChildComponent ref="child" />
</template>

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

const child = ref(null);

const openModal = () => {
  if (child.value) {
    child.value.isOpen = true;
  }
};
</script>

In this example, the ChildComponent exposes its isOpen property using defineExpose. The ParentComponent can then access this property using the ref attribute and toggle the modal's visibility.

Notifying the Parent about an Event

While sharing a variable with the parent can be useful, it's not always the desired approach. Sometimes, you want the child component to notify the parent about an event without exposing its internal state. This is where defineEmits comes handy, as it allows a child component to emit custom events that the parent component can listen to. Here's an example:

// ChildComponent.vue
<template>
  <div v-if="isOpen">Modal is open!</div>
  <button @click="closeModal">Close</button>
</template>

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

const isOpen = ref(false);
const emit = defineEmits(['opened', 'closed']);

const openModal = () => {
  isOpen.value = true;
  emit('opened');
};

const closeModal = () => {
  isOpen.value = false;
  emit('closed');
};
</script>
// ParentComponent.vue
<template>
  <button @click="openModal">Open Modal</button>
  <ChildComponent @opened="onOpened" @closed="onClosed" />
</template>

<script setup>
const onOpened = () => {
  console.log('Modal opened!');
};

const onClosed = () => {
  console.log('Modal closed!');
};

const openModal = () => {
  console.log('Just do it ✔︎');
};
</script>

In this example, the ChildComponent emits opened and closed events when the modal is toggled. The ParentComponent listens to these events using the @opened and @closed attributes and responds accordingly.

Communicating between components is very important in building robust and interactive Vue applications. By using defineExpose and defineEmits, you can create maintainable component interactions that enhance user experience. While I prefer using defineEmits for its decoupling benefits, I still use defineExpose sometimes when you I to access a child component's properties directly for whatever reason. Choose the approach that best fits your use case. Hope this was any combination of being informative and fun to read!

Wanna chat about what you just read, or anything at all? Click here to tweet at me on 𝕏