Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Component events: emit

Data flows from parent to child through props, and messages go the other way through events. The child emits an event, for example "this task should be deleted", and the parent listens to it with @ just like a button click and changes the data itself.

Declaring events

Add the defineEmits macro with a list of events to src/components/TodoItem.vue:

<!-- src/components/TodoItem.vue -->
<script setup>
defineProps({
  todo: { type: Object, required: true },
})
 
const emit = defineEmits(['remove'])
</script>
 
<template>
  <li>
    {{ todo.text }}
    <button @click="emit('remove')">Delete</button>
  </li>
</template>

Clicking the button calls emit('remove') — the component announces that the user wants to delete the task. It deletes nothing itself.

Handling in the parent

<!-- src/App.vue -->
<script setup>
import { ref } from 'vue'
import TodoItem from './components/TodoItem.vue'
 
const todos = ref([
  { id: 1, text: 'Buy bread', done: false },
  { id: 2, text: 'Call Anna', done: false },
])
 
function removeTodo(id) {
  todos.value = todos.value.filter((todo) => todo.id !== id)
}
</script>
 
<template>
  <ul>
    <TodoItem
      v-for="todo in todos"
      :key="todo.id"
      :todo="todo"
      @remove="removeTodo(todo.id)"
    />
  </ul>
</template>

The "Delete" button in each row removes its own task. The parent knows the id from the loop and passes it to the function.

Data with the event

The second argument of emit passes a value. Let's add a toggle event that reports the checkbox's new state:

<!-- src/components/TodoItem.vue -->
<script setup>
defineProps({
  todo: { type: Object, required: true },
})
 
const emit = defineEmits(['remove', 'toggle'])
</script>
 
<template>
  <li>
    <input
      type="checkbox"
      :checked="todo.done"
      @change="emit('toggle', $event.target.checked)"
    >
    {{ todo.text }}
    <button @click="emit('remove')">Delete</button>
  </li>
</template>
<!-- src/App.vue, template fragment -->
<TodoItem
  v-for="todo in todos"
  :key="todo.id"
  :todo="todo"
  @toggle="(checked) => (todo.done = checked)"
  @remove="removeTodo(todo.id)"
/>

Ticking the box emits toggle with true or false, and the parent writes the value into the task.

Data flow

DirectionMechanismExample
Parent → childprops:todo="todo"
Child → parentemit@remove="removeTodo(todo.id)"

This rule — data down, events up — makes it clear who changes state and where.

Common mistake

Forgetting to declare an event in defineEmits. If there is a list of events but the new one is missing from it, Vue prints a console warning. If there is no defineEmits at all, the parent's listener lands on the component's root element as a plain DOM handler. For an event named click this means the handler fires twice: once from emit and once from the real click. Declare every event a component emits.

Thanks for reading!

Donate to the site — we work hard for you.