Avoiding EventBus Pitfalls in Vue 3 with mitt
Learn how to prevent duplicate API calls and event name collisions when using mitt as a global event bus in Vue 3 with the Composition API.

Avoiding EventBus Pitfalls in Vue 3 with 'mitt'
When building reactive and dynamic interfaces in Vue 3, you might turn to a lightweight event bus like mitt to communicate across components. It's clean, simple, and integrates easily with the Composition API.
But using mitt carelessly can lead to subtle and frustrating bugs—like unnecessary API calls or event collisions.
In this article, I’ll share a real-world debugging experience and show how to use mitt correctly to avoid memory leaks, redundant listeners, and naming conflicts.
The Bug: Redundant API Calls
Initially, I had this code in a Vue 3 component:
import emitter from '@/plugins/mitt';
onMounted(() => {
emitter.on('search-by-filters', () => {
fetchData();
});
});
Everything seemed fine—until I started navigating between pages that used the same component structure. Suddenly, every search-by-filters
event started triggering multiple times, making duplicate API requests.
The Root Cause
- I never cleaned up the event listener. The global emitter kept the old handlers in memory.
- I used unscoped event names.
The same
'goToPage'
name was used in multiple places, causing unintended side effects.
The Fix: Clean Up and Scope Your Events
1. Remove Listeners on Component Unmount
onBeforeUnmount(() => {
emitter.off('search-by-filters');
emitter.off(`${scope}goToPage`);
});
2. Scope Your Event Names
const scope = 'searchResults-';
emitter.on(`${scope}goToPage`, goToPage);
This prevents your component from listening to events that don’t belong to it.
Pro Tip: Create a Scoped Wrapper
To make this cleaner, you can wrap mitt
in a scoped utility:
export function useScopedEmitter(scope: string) {
return {
on: (event: string, handler: any) => emitter.on(`${scope}${event}`, handler),
off: (event: string, handler?: any) => emitter.off(`${scope}${event}`, handler),
emit: (event: string, payload: any) => emitter.emit(`${scope}${event}`, payload)
};
}
Summary
If you're using mitt
with Vue 3, remember:
Always clean up listeners in onBeforeUnmount
Scope your event names to avoid collisions
Consider wrapping mitt
in a scoped utility
These best practices can prevent memory leaks, improve performance, and keep your codebase clean and understandable.