Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Components  ›  Capstone Project

Events — Messages Flowing Up

Events 14 minA child announces something with defineEmits and emit, and the parent listens with v-on and decides what to do.

Events — Messages Flowing Up

Each BookCard now shows its book, and you want a Remove button on it. But the card cannot change the list: the books array lives in App.vue, and props are read-only. The card needs a way to say "please remove me" and let the owner act. That is a component event.

The rule

The child emits a message up; the parent listens and decides.

The child declares the events it can send with defineEmits, another macro that needs no import, and sends one with emit:

<!-- BookCard.vue -->
<script setup>
const props = defineProps({ id: Number, title: String, read: Boolean })
const emit = defineEmits(['remove', 'toggleRead'])
</script>

<template>
  <article class="card">
    <h3>{{ title }}</h3>
    <button @click="emit('toggleRead', id)">Mark read</button>
    <button @click="emit('remove', id)">Remove</button>
  </article>
</template>

The first argument is the event name; anything after it is the payload, handed to the listener. The parent listens with @ (short for v-on), just like a click:

<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import BookCard from './components/BookCard.vue'

const books = ref([
  { id: 1, title: 'Dune', read: true },
  { id: 2, title: 'Circe', read: false }
])

function removeBook(id) {
  books.value = books.value.filter(b => b.id !== id)
}

function toggleRead(id) {
  const book = books.value.find(b => b.id === id)
  book.read = !book.read
}
</script>

<template>
  <BookCard v-for="b in books" :key="b.id" v-bind="b"
            @remove="removeBook" @toggle-read="toggleRead" />
</template>

(v-for repeats the card once per book; the Directives section covers it.) Notice toggleRead in the child and @toggle-read in the parent. Vue matches camelCase emits with kebab-case listeners, the same way it does for props.

Two facts that surprise people:

  • Component events do not bubble. A click on a button bubbles up through the DOM, but emit('remove') reaches only the direct parent. If a grandparent needs it, the parent must listen and emit again.
  • Declaring emits matters. Events listed in defineEmits are treated as component events. An undeclared listener such as @remove would instead fall through to the card's root element as a native listener (you will see that in Attribute Inheritance).

The shortcut

Combine it with the props lesson into one loop: props down, events up, owner changes.

Clicking Remove on "Circe"Step 1 of 4
  1. The child reports

    The Remove button runs emit('remove', 2). The card changes nothing itself.

Drills

Answer fast — trust the rule.

quick drill

How does BookCard send a remove event carrying the book's id?

quick drill

The child emits markFinished. How does the parent listen in its template?

quick drill

BookCard sits inside BookList, which sits inside App. BookCard emits remove. Who hears it?

quick drill

Why should the card emit remove instead of deleting the book from the list itself?

Remember

  • defineEmits declares events; emit(name, payload) sends one; the parent uses @name.
  • Shortcut: props down, events up, owner changes.
  • Trap: expecting component events to bubble past the direct parent.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainRemoving a book

Shelf's BookCard has a Remove button, but the books array lives in App.vue. Explain, with code, how clicking Remove deletes the right book.