defineEmits()

The defineEmits() macro is used to declare and validate events emitted from a child to parent component. Single or multiple events can be listed in an array or, use the object syntax for event validation.

It is only available to use inside of <script setup>, and importing is not required. Using defineEmits() also provides an easy to read overview of the components event handling.

If not using <script setup>, the equivilent is to use the emits option inside of export default.

Syntax

//  Array syntax
const emit = defineEmits(['event1', 'event2'])

// Object syntax
const emit = defineEmits({
  eventName: (payload) => {
    // validation
    return true; // or false to prevent emitting event
  }
});

Parameters:

  • Array: An array of strings containing event names (simple syntax).
  • Object: An object containing events, each key is an event name. Accepting a function that validates the event payload and returns a boolean.

Basic usage

Here's a simple example of defining emits for a button component, using the array syntax. This component emits a click event when the button is clicked:

<script setup>
const emit = defineEmits(['click', 'hover']);

const handleClick = () => {
  emit('click', { timestamp: Date.now() });
};

const handleHover = () => {
  emit('hover');
};
</script>

<template>
  <button @click="handleClick" @mouseenter="handleHover">
    Click me
  </button>
</template>

The events are listened to in the parent component:

<template>
  <CustomButton
    @click="handleButtonClick"
    @hover="handleButtonHover"
  />
</template>

<script setup>
const handleButtonClick = (payload) => {
  console.log('Button clicked at:', payload.timestamp);
};

const handleButtonHover = () => {
  console.log('Button hovered');
};
</script>

The emit() function accepts two parameters. The event name (string) and an optional payload. The parent component can listen to these events using the @eventName syntax or v-on:eventName.

Type validation

Vue provides type checking for emitted events. This can be useful to ensure the correct data types are emitted from a component:

const emit = defineEmits({
  submit: (payload) => {
    // Check payload is an object
    // amd includes email and password in the payload
    return payload && typeof payload === 'object' &&
           'email' in payload && 'password' in payload;
  },
  update: (payload) => {
    // Check payload is a number
    return typeof payload === 'number' && payload >= 0;
  }
});

Event validation

You can add custom validation, and these functions will return a boolean value. This example checks the email and password are structured correctly before emitting to the parent component:

const emit = defineEmits({
  submit: (payload) => {
    if (!payload.email || !payload.email.includes('@')) {
      console.warn('Invalid email provided');
      return false;
    }
    if (!payload.password || payload.password.length < 8) {
      console.warn('Password must be at least 8 characters');
      return false;
    }
    return true;
  },
});

Multiple event types

Multiple events can have different validation rules:

const emit = defineEmits({
  // Simple event with no validation
  close: () => true,

  // Payload must be an object, with 'data' and 'timestamp' present
  save: (payload) => {
    return payload && typeof payload === 'object' &&
           'data' in payload && 'timestamp' in payload;
  },

  // More complex validation
  delete: (payload) => {
    if (!payload.id) {
      console.error('An ID is required to delete');
      return false;
    }
    if (payload.confirm !== true) {
      console.warn('Confirm to delete');
      return false;
    }
    return true;
  }
});

Using with TypeScript

When using TypeScript, you can also define emits with types:

const emit = defineEmits<{
  (e: 'update', value: string): void
  (e: 'delete', id: number): void
  (e: 'submit', data: { email: string; password: string }): void
}>();

// Usage
emit('update', 'new value');
emit('delete', 123);
emit('submit', { email: 'user@example.com', password: 'secure123' });

Practical example

The following example shows how defineEmits() can be used in a form component. The form events call various functions, these provide validation before emitting the custom event.

<script setup>
const emit = defineEmits({
  submit: (payload) => {
    return payload && payload.email && payload.password;
  },
  cancel: () => true,
  validationError: (payload) => {
    return payload && Array.isArray(payload.errors);
  }
});

const formData = ref({
  email: '',
  password: ''
});

const errors = ref([]);

const handleSubmit = () => {
  errors.value = [];

  if (!formData.value.email) {
    errors.value.push('Email required');
  }
  if (!formData.value.password) {
    errors.value.push('Password required');
  }

  if (errors.value.length > 0) {
    emit('validationError', { errors: errors.value });
    return;
  }

  emit('submit', { ...formData.value });
};

const handleCancel = () => {
  emit('cancel');
};
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="formData.email" type="email" placeholder="Email" />
    <input v-model="formData.password" type="password" placeholder="Password" />
    <button type="submit">Submit</button>
    <button type="button" @click="handleCancel">Cancel</button>
  </form>
</template>

The parent component listens for these custom events and calls required functions:

<template>
  <SignUpForm
    @submit="handleFormSubmit"
    @cancel="handleFormCancel"
    @validation-error="handleValidationError"
  />
</template>

<script setup>
const handleFormSubmit = (data) => {
  console.log('Form submitted:', data);
};

const handleFormCancel = () => {
  console.log('Form cancelled');
};

const handleValidationError = (payload) => {
  console.log('Validation errors:', payload.errors);
};
</script>