Vue Tip: Emit Event From Composable
Michael Hoffmann
@mokkapps
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:
<script setup>
const emit = defineEmits(['change', 'delete'])
</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:
import { ref } from 'vue';
export const useEmitOnRefChange = () => {
const counter = ref(0);
const increment = () => {
counter.value++;
};
return {
counter,
increment,
};
};
In our Vue component, we would watch the counter value and emit the event when the counter changes:
<script setup lang="ts">
import { watch } from 'vue';
import { Emits } from '../types';
import { useEmitOnRefChange } from '../composables/useEmitOnRefChange';
const emit = defineEmits<Emits>();
const { counter, increment } = useEmitOnRefChange();
watch(counter, (newCounter) => {
emit('test', `emit-ref-change-${newCounter}`);
});
</script>
<template>
<button @click="increment">
Trigger emit by watching a composable ref
</button>
</template>
2. Pass emit as a parameter
Another solution would be to pass the emit
object as an argument to the composable:
import { Emits } from '../types';
export const useEmitParameter = (emit: Emits) => {
const triggerEmit = () => {
emit('test', `emit-parameter-${Math.floor(Math.random() * 101)}`);
};
return {
triggerEmit,
};
};
<script setup lang="ts">
import { watch } from 'vue';
import { Emits } from '../types';
import { useEmitParameter } from '../composables/useEmitParameter';
const emit = defineEmits<Emits>();
const { triggerEmit } = useEmitParameter(emit);
</script>
<template>
<button @click="triggerEmitParameter">
Trigger emit from composable with emit as parameter
</button>
</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:
import { getCurrentInstance } from 'vue';
import { Emits } from '../types';
export const useEmitFromCurrentInstance = () => {
const { emit }: { emit: Emits } = getCurrentInstance();
const triggerEmit = () => {
emit('test', `emit-current-instance-${Math.floor(Math.random() * 101)}`);
};
return {
triggerEmit,
};
};
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 BlueSky to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue & Nuxt newsletter :
Vue Tip: Use VueUse to Unleash the Power of Utility Functions
Vue Tip: Split Your SFC into Multiple Files