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="ts">
2import { watch } from 'vue';
3import { Emits } from '../types';
4import { useEmitParameter } from '../composables/useEmitParameter';
5
6const emit = defineEmits<Emits>();
7
8const { triggerEmit } = useEmitParameter(emit);
9</script>
10
11<template>
12    <button @click="triggerEmitParameter">
13      Trigger emit from composable with emit as parameter
14    </button>
15</template>

3. Use getCurrentInstance

The last solution is to use the getCurrentInstance function from Vue to get the emit object from the current component instance:

composable.ts
1import { getCurrentInstance } from 'vue';
2import { Emits } from '../types';
3
4export const useEmitFromCurrentInstance = () => {
5  const { emit }: { emit: Emits } = getCurrentInstance();
6
7  const triggerEmit = () => {
8    emit('test', `emit-current-instance-${Math.floor(Math.random() * 101)}`);
9  };
10
11  return {
12    triggerEmit,
13  };
14};

Use with caution

getCurrentInstance is an undocumented internal API that you should use with caution.

It was originally documented in October 2020 but later removed in August 2021.

StackBlitz Demo

All three solutions are implemented in this StackBlitz demo:

If you liked this Vue tip, follow me on X to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue & Nuxt newsletter:

I will never share any of your personal data. You can unsubscribe at any time.