Skip to the solution
MediumFrontend8 min read

Why declare emits and can you validate them?

978 views

The problem

Question presented to candidate: "A teammate's BaseButton emits click and the parent's handler runs twice per click. Another component emits submit with a payload that sometimes has no email. Explain why declaring emits fixes the first bug, how you would validate the second, and what validation can and cannot do."

What a strong answer should cover:

  • emit() works without declaring, but declaring (emits option or defineEmits) documents the component's API and enables typing.
  • Declared events are removed from $attrs. An undeclared click listener falls through to the root element as a native listener, so it fires twice: once from emit, once from the DOM.
  • When emits is declared, emitting an undeclared event logs a dev warning, which catches typos.
  • Object syntax adds a validator per event: return false and Vue warns. The event is still delivered, and production builds skip the check.
  • Component events do not bubble; only the direct parent's listener runs.

Clarifying questions expected:

  • "Is this <script setup>? Then I would use defineEmits, and a type-based declaration in TypeScript."

Code / implementation expected: Yes. A component showing the double-fire problem and an emits object with a validator.

  • emits

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 11 sections

Read the code

VueUndeclared vs declared click, event validation and a typo warning
<script setup>
import { ref, defineComponent, h } from "vue";

// Emits "click" but does NOT declare it
const UndeclaredButton = defineComponent({
  setup(_, { emit, attrs }) {
    return () =>
      h("button", { class: "undeclared", onClick: () => emit("click", "from emit") },
        "attrs: " + Object.keys(attrs).join(", "));
  },
});

// Same component, but "click" is declared
const DeclaredButton = defineComponent({
  emits: ["click"],
  setup(_, { emit, attrs }) {
    return () =>
      h("button", { class: "declared", onClick: () => emit("click", "from emit") },
        "attrs: " + (Object.keys(attrs).join(", ") || "none"));
  },
});

// Object syntax: a validator per event. Return false to get a dev warning.
const SignupForm = defineComponent({
  emits: {
    submit: (payload) => typeof payload?.email === "string" && payload.email.includes("@"),
    cancel: null, // declared, no validation
  },
  setup(_, { emit }) {
    return () => [
      h("button", { class: "good", onClick: () => emit("submit", { email: "ada@example.com" }) }, "valid"),
      h("button", { class: "bad", onClick: () => emit("submit", { email: "nope" }) }, "invalid"),
      h("button", { class: "typo", onClick: () => emit("cancle") }, "typo"),
    ];
  },
});

const log = ref([]);
</script>

<template>
  <UndeclaredButton @click="(arg) => log.push('undeclared got ' + (typeof arg === 'string' ? arg : 'a native event'))" />
  <DeclaredButton @click="(arg) => log.push('declared got ' + (typeof arg === 'string' ? arg : 'a native event'))" />
  <SignupForm @submit="(p) => log.push('submit ' + p.email)" />
  <ol><li v-for="(line, i) in log" :key="i">{{ line }}</li></ol>
</template>
VuedefineEmits with a validator: dev warning, production silence
<!-- Form.vue -->
<script setup>
const emit = defineEmits({
  submit: (payload) => {
    console.log("  [validator ran]");
    return typeof payload?.email === "string" && payload.email.includes("@");
  },
});
</script>
<template><button @click="emit('submit', { email: 'nope' })">send</button></template>

<!-- App.vue -->
<script setup>
import Form from "./Form.vue";
</script>
<template><Form @submit="(p) => console.log('  parent received', p.email)" /></template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 31 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

What are provide and inject?

Continue