Javascript is required
ยท
2 min read

Vue Tip: Emit Event From Composable

Vue Tip: Emit Event From Composable Image

Your application may contain a lot of components that are using the same logic. For example, you may have a lot of components that emit the same event. In this case, it might be tempting to create a composable that will emit this event and is used in all components.

If you are using Vue 3 with the <script setup> you define your emits using the defineEmits compiler macro:

App.vue
1<script setup>
2const emit = defineEmits(['change', 'delete'])
3</script>

defineEmits is a compiler macro that is only usable inside <script setup> and compiled away when <script setup> is processed. Thus, we cannot use it inside a composable.

Let's take a look at three possible solutions to emit an event inside a composable without using defineEmits inside of it.

1. Emit from your component

In general, I would always recommend emitting from your component. This is the most straightforward solution and it is easy to understand what is happening.

So you would have a composable with some logic, in this example the composable returns a reactive counter variable and a function to increment the counter:

composable.ts
1import { ref } from 'vue';
2
3export const useEmitOnRefChange = () => {
4  const counter = ref(0);
5
6  const increment = () => {
7    counter.value++;
8  };
9
10  return {
11    counter,
12    increment,
13  };
14};

In our Vue component, we would watch the counter value and emit the event when the counter changes:

App.vue
1<script setup lang="ts">
2import { watch } from 'vue';
3import { Emits } from '../types';
4import { useEmitOnRefChange } from '../composables/useEmitOnRefChange';
5
6const emit = defineEmits<Emits>();
7
8const { counter, increment } = useEmitOnRefChange();
9watch(counter, (newCounter) => {
10  emit('test', `emit-ref-change-${newCounter}`);
11});
12</script>
13
14<template>
15    <button @click="increment">
16      Trigger emit by watching a composable ref
17    </button>
18</template>

2. Pass emit as a parameter

Another solution would be to pass the emit object as an argument to the composable:

composable.ts
1import { Emits } from '../types';
2
3export const useEmitParameter = (emit: Emits) => {
4  const triggerEmit = () => {
5    emit('test', `emit-parameter-${Math.floor(Math.random() * 101)}`);
6  };
7
8  return {
9    triggerEmit,
10  };
11};
App.vue
1<script setup lang