JAVASCRIPT

Create a Simple Event Bus Composable

Build a lightweight, reusable event bus using Vue 3's Composition API to facilitate communication between unrelated components without prop drilling or global state.

<!-- EventBusEmitter.vue -->
<template>
  <div style="border: 1px solid gray; padding: 10px; margin: 10px;">
    <h3>Event Emitter Component</h3>
    <button @click="emitCustomEvent">Emit 'my-custom-event'</button>
    <p>Message to emit: <input type="text" v-model="messageToEmit"></p>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { useEventBus } from './useEventBus.js';

const { emit } = useEventBus();
const messageToEmit = ref('Hello from Emitter!');

const emitCustomEvent = () => {
  emit('my-custom-event', messageToEmit.value);
  console.log('Event emitted:', messageToEmit.value);
};
</script>

<!-- EventBusListener.vue -->
<template>
  <div style="border: 1px solid gray; padding: 10px; margin: 10px;">
    <h3>Event Listener Component</h3>
    <p>Received Message: {{ receivedMessage }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useEventBus } from './useEventBus.js';

const { on, off } = useEventBus();
const receivedMessage = ref('No event received yet.');

const handleCustomEvent = (payload) => {
  receivedMessage.value = payload;
  console.log('Event received:', payload);
};

onMounted(() => {
  on('my-custom-event', handleCustomEvent);
});

onUnmounted(() => {
  off('my-custom-event', handleCustomEvent);
});
</script>

<!-- useEventBus.js (composable file) -->
import { getCurrentInstance } from 'vue';

// Use a symbol for the global property key to avoid collisions
const EVENT_BUS_GLOBAL_KEY = Symbol('myAppEventBus');

export function useEventBus() {
  const app = getCurrentInstance()?.appContext.app;
  if (!app) {
    // Should not happen if called in setup, but good for safety
    console.warn('useEventBus must be called within a component setup context.');
    return { on: () => {}, off: () => {}, emit: () => {} };
  }

  // Ensure the event bus Map is initialized on global properties only once per app instance
  if (!app.config.globalProperties[EVENT_BUS_GLOBAL_KEY]) {
    app.config.globalProperties[EVENT_BUS_GLOBAL_KEY] = new Map();
  }

  const internalBus = app.config.globalProperties[EVENT_BUS_GLOBAL_KEY];

  const on = (event, callback) => {
    if (!internalBus.has(event)) {
      internalBus.set(event, new Set());
    }
    internalBus.get(event).add(callback);
  };

  const off = (event, callback) => {
    if (internalBus.has(event)) {
      internalBus.get(event).delete(callback);
      // Optional: Clean up the event's Set if it becomes empty
      if (internalBus.get(event).size === 0) {
        internalBus.delete(event);
      }
    }
  };

  const emit = (event, ...args) => {
    if (internalBus.has(event)) {
      // Iterate over a copy of the Set to prevent issues if listeners modify it
      [...internalBus.get(event)].forEach(callback => callback(...args));
    }
  };

  return { on, off, emit };
}
How it works: This snippet provides a reusable Vue 3 Composable (`useEventBus.js`) that implements a simple event bus pattern. It allows components to communicate with each other regardless of their position in the component tree, without relying on props or emits. The `on` function registers a listener for an event, `off` removes it, and `emit` triggers an event, passing any number of arguments. The event bus itself is stored as a `Map` within Vue's global properties, ensuring it's a singleton accessible across the entire application, making it useful for global notifications or inter-component communication that doesn't fit standard prop/emit patterns. Listeners should be cleaned up using `onUnmounted`.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs