Skip to content
← All questions
Intermediate

Why do undeclared emits fire twice?

ComponentsCommon Errors

Because when you re-emit a native event (like click) without declaring it in defineEmits, the parent's listener ends up attached in two places: once through $attrs fallthrough on the root element, and once through your explicit $emit() call.

vue
<!-- MyButton.vue — NO defineEmits -->
<template>
  <button @click="$emit('click', $event)">
    <slot />
  </button>
</template>

<!-- Parent.vue -->
<MyButton @click="handleClick">Click me</MyButton>

What happens on each click:

  1. Native click fires on the <button>
  2. Since click is not declared in emits, @click from the parent falls through to the root element via $attrs, firing handleClick
  3. The @click="$emit('click', $event)" also fires, emitting a component event that triggers handleClick again

Result: handleClick runs twice.

How to fix it

Option 1: Declare the emit. This tells Vue that @click on the component is a component event, not a native one, so it won't fall through.

vue
<script setup>
const emit = defineEmits<{ click: [event: MouseEvent] }>()
</script>

<template>
  <button @click="emit('click', $event)">
    <slot />
  </button>
</template>

Option 2: Don't re-emit at all. If the component has a single root element, the native event falls through automatically.

vue
<!-- MyButton.vue — no emit needed, click falls through to <button> -->
<template>
  <button>
    <slot />
  </button>
</template>

<!-- Parent.vue — works, fires once -->
<MyButton @click="handleClick">Click me</MyButton>

The rule is simple: if you explicitly $emit a native event name, you must declare it in defineEmits. Otherwise the listener exists in two places.

Released under the MIT License.