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.

Vue 3 mitt eventbus communication

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

  1. I never cleaned up the event listener. The global emitter kept the old handlers in memory.
  2. 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.