Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 54

Emitting Custom Events: Child to Parent Communication

Recommended resources

Hello! Welcome back to our journey with Vue.js.

In our last lesson, we focused on the first half of Vue's fundamental communication pattern: passing data down from a parent to a child using props. You learned how to define, validate, and pass props, and you enhanced our to-do app to display an item's priority.

Today, we'll complete the loop. We'll master the other half of the "props down, events up" pattern: communicating from a child component back up to its parent by emitting custom events. This is the key to making our components truly interactive. When a user clicks a button inside a child component, it's an event emission that tells the parent what action to take.

This lesson directly addresses your goal of understanding the "why" behind development practices by explaining how to build a clean, predictable data flow in your applications.

By the end of this lesson, you will be able to:

  • Explain the role of custom events in child-to-parent communication.
  • Use the defineEmits macro to declare and trigger events in a child component.
  • Pass data (a "payload") along with an event.
  • Listen for and handle custom events in a parent component.

Let's dive in and give our components a voice.


Part 1: The "Events Up" Pattern

In the last lesson, you saw a diagram showing that props flow down. Now let's focus on the other direction. When a child needs to communicate something to its parent—like "the user clicked me!" or "here's the data from the form I'm in"—it does so by emitting an event.

Vue.js Child-to-Parent Communication with \$emit
This diagram shows the core idea: a child component uses the `\$emit` function to send a signal (the event) up to its parent, which is listening for it. The event can carry data with it.

The parent component can then "listen" for this specific event and run a function when it occurs. This keeps the data flow clean: the child doesn't directly change the parent's data; it just reports that something happened and provides relevant information. The parent remains in control of its own state.

To understand this flow and the syntax involved, let's start with a foundational article.

How to Emit an Event From Child to Parent Component in Vue: \$emit

The article 'How to Emit an Event From Child to Parent Component in Vue' provides a great overview of this concept. It clearly explains the roles of the child and parent in this communication.

Please read the first few sections, up to and including 'Time to Handle Child Component Events in the Parent'. Focus on understanding the three main steps: defining an event, emitting it from the child, and listening for it in the parent.



Part 2: Implementation with <script setup>

Now that you have the conceptual overview, let's look at the modern implementation using the Composition API with <script setup>, which is the standard for new Vue 3 projects.

The two key pieces of the puzzle are:

  1. defineEmits: A macro used inside <script setup> to declare which events the component can emit. This is a best practice as it documents the component's public interface. It returns an emit function that you'll use to trigger the events.
  2. emit(): The function you call to actually send the event.

Here is the flow:

In the Child Component (ChildComponent.vue):

<script setup>
// 1. Declare the events your component can emit.
// This returns the 'emit' function.
const emit = defineEmits(['someEvent', 'anotherEvent']);

function doSomething() {
  // 2. Call emit with the name of the event you want to send.
  emit('someEvent'); 
}
</script>

<template>
  <button @click="doSomething">Click Me</button>
</template>

In the Parent Component (ParentComponent.vue):

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

function handleTheEvent() {
  // 3. This method runs when the event is received.
  console.log('Child component emitted someEvent!');
}
</script>

<template>
  <!-- Use v-on (or @) to listen for the custom event -->
  <ChildComponent @some-event="handleTheEvent" />
</template>

Notice the naming convention: we define the event in camelCase (someEvent) but listen for it in kebab-case (@some-event). This is the same convention we use for props and is the recommended practice.



Part 3: Sending Data with Events (Payloads)

Emitting an event is useful, but it's far more powerful when you can send data along with it. This data is often called the event's payload.

To send a payload, you simply pass it as the second argument to the emit function:

emit('eventName', payload)

The payload can be any type of data: a string, a number, a boolean, or even an entire object.

The parent component automatically receives this payload as the first argument to its handler method.

Let's watch a short video that clearly demonstrates how to emit an event both with and without a payload.

3 Ways to Send Events with VUE EMIT

The video '3 Ways to Send Events with VUE EMIT' from LearnVue is a concise and clear explanation of the emit function and its arguments.

Watch from the beginning until 03:03. Pay close attention to the second argument of the emit function, which is the payload. While the video shows three ways (inline, Options API, Composition API), focus on the Composition API example (starting around 02:26) as it's what we are using.

As the video also mentions, explicitly declaring your emits is a best practice for documentation and clarity, which aligns with our goal of understanding professional coding standards.



Part 4: Mini-Project: Implementing the Delete Function

It's time to put theory into practice. In our last lesson, we added a "Delete" button to our TodoItem.vue component. Now, let's make it work. The goal is for the TodoItem (child) to tell App.vue (parent) that it should be deleted, and to specify which item to delete.

This is a perfect use case for an event with a payload!

Step 1: Update the Child Component (src/components/TodoItem.vue)

Open TodoItem.vue and modify the <script setup> block. Right now, the delete button tries to call emit, but we haven't actually defined it yet.

  1. Declare the event: Use defineEmits to declare that this component can emit a delete-todo event.
  2. Ensure emit is used: The template from our previous lesson should already have the @click handler calling emit. Let's confirm it's correct.

Your <script setup> block should look like this:

// src/components/TodoItem.vue
import { defineProps, defineEmits } from 'vue'; // Make sure defineEmits is imported

const props = defineProps({
  todo: {
    type: Object,
    required: true
  },
  priority: {
    type: String,
    default: 'low'
  }
});

// 1. Declare the event and get the emit function
const emit = defineEmits(['delete-todo']);

And your template's delete button should look like this:

<!-- src/components/TodoItem.vue -->
<button class="delete-btn" @click="emit('delete-todo', props.todo.id)">Delete</button>

When this button is clicked, it will now correctly emit a delete-todo event and pass the unique ID of that to-do item as the payload.

Step 2: Update the Parent Component (src/App.vue)

Now, open App.vue. We need to listen for the delete-todo event and create a method to handle it.

  1. Create the handler method: Add a new function called deleteTodo inside <script setup>. This function will take an id as its argument and update the todos array by filtering out the item with the matching ID.
  2. Listen for the event: Find the <TodoItem> component in your template and add a v-on listener (@delete-todo) that calls your new deleteTodo method.

Your <script setup> in App.vue should be updated as follows:

// src/App.vue
import { ref } from 'vue';
import TodoItem from './components/TodoItem.vue';

const todos = ref([
  { id: 1, text: 'Learn Vue', done: true, priority: 'high' },
  { id: 2, text: 'Build a project', done: false, priority: 'medium' },
  { id: 3, text: 'Refactor code', done: false }
]);

// 1. Add this handler method
function deleteTodo(id) {
  // We reassign todos.value to a new array that doesn't include the deleted todo
  todos.value = todos.value.filter(todo => todo.id !== id);
}

And update your App.vue template:

<!-- src/App.vue -->
<TodoItem 
  v-for="todo in todos" 
  :key="todo.id"
  :todo="todo"
  :priority="todo.priority"
  @delete-todo="deleteTodo"  /* <-- 2. Add this event listener */
/>

Now, run your application. When you click the "Delete" button on any to-do item, it should disappear from the list. You have successfully implemented the full "props down, events up" communication pattern!



Conclusion

Fantastic work! You have now mastered the complete communication cycle between parent and child components. This pattern is the bedrock of building complex, yet manageable, Vue applications.

Key Takeaways:

  • "Props Down, Events Up": This is the core principle for a predictable data flow in Vue.
  • Child-to-Parent Communication: Achieved by the child emitting a custom event.
  • defineEmits: Use this macro in <script setup> to declare events and get the emit function. It's a crucial best practice.
  • Event Payloads: Pass data from the child to the parent by including a second argument in the emit call.
  • Event Listeners: The parent uses the @ (or v-on:) directive to listen for custom events and trigger a handler method.

In our next lesson, we will explore a powerful feature that is built directly on top of this props-and-events pattern: using v-model on custom components. You'll see how this syntactic sugar can make building reusable form inputs and other two-way-bound components incredibly simple and elegant.