Event Bus
An event bus lets components communicate without any direct reference to each other. One component emits an event; another component (anywhere in the app) listens for it. This is the publish-subscribe pattern, and it’s the most decoupled form of component communication React offers — because it doesn’t use React at all.
Custom events with the DOM
Section titled “Custom events with the DOM”The browser already has an event system. You can dispatch a custom event on window (or any DOM element) and listen for it elsewhere.
// Emitter — dispatches a custom event on the window objectfunction ThemeToggle() { function toggle() { window.dispatchEvent( new CustomEvent('theme:toggle', { detail: { theme: 'dark' } }) ) } return <button onClick={toggle}>Dark mode</button>}The emitter component knows nothing about who listens. It fires the event on window and continues.
// Listener — subscribes to the same event, must clean up on unmountfunction StatusBar() { useEffect(() => { const handler = (e: CustomEvent) => { console.log('Theme changed to', e.detail.theme) } window.addEventListener('theme:toggle', handler) return () => window.removeEventListener('theme:toggle', handler) }, []) return <div>Status Bar</div>}No common ancestor needed. No provider wrapping the tree. The emitter and listener can be anywhere in the component hierarchy — or even outside React entirely.
Event bus libraries
Section titled “Event bus libraries”For more structure, libraries like mitt provide a typed event bus in under 200 bytes.
import mitt from 'mitt'
type Events = { 'theme:toggle': { theme: string } 'user:login': { userId: string } 'notification:show': { message: string; type: 'info' | 'error' }}
const bus = mitt<Events>()
// Any component can emitbus.emit('user:login', { userId: 'abc-123' })
// Any component can listenuseEffect(() => { bus.on('user:login', ({ userId }) => fetchUserProfile(userId)) return () => bus.off('user:login', handler) // cleanup!}, [])Mitt exposes three methods: on, off, and emit. No classes, no instantiation ceremony. Just a typed map of event names to handlers.
The Node.js EventEmitter pattern (used by libraries like React Native’s DeviceEventEmitter) works the same way but with a class API. If you need wildcards or more sophisticated event handling, eventemitter3 is a popular choice.
Trade-offs vs context vs props
Section titled “Trade-offs vs context vs props”- Data flow — Props: Explicit, one-directional. Context: Implicit, top-down. Event Bus: Fully decoupled.
- Traceability — Props: Call site shows everything. Context: Provider in tree. Event Bus: No caller visibility.
- Re-render scope — Props: Child subtree. Context: All consumers. Event Bus: None (manual control).
- Type safety — Props: Native. Context: Native. Event Bus: Depends on implementation.
- Performance cost — Props: Props that change re-render. Context: All consumers re-render. Event Bus: Listener registration only.
- React integration — Props: Native. Context: Native. Event Bus: Requires manual lifecycle.
The event bus wins on decoupling and re-render avoidance. It loses on traceability — nothing in the source code tells you who emits or listens to a given event. This makes debugging harder as the app grows. An event that five components emit and eight listen to is hard to reason about.
When to use an event bus
Section titled “When to use an event bus”Event buses work well for:
- Cross-cutting UI events — toast notifications, global loading states, keyboard shortcuts.
- Cross-window communication — syncing state between browser tabs using
BroadcastChannelorstorageevents. - Third-party integration — a script tag in the page that needs to notify your React app about something.
- Legacy code interop — bridging a jQuery plugin or vanilla JS module to React state.
Try it yourself
Section titled “Try it yourself”References
Section titled “References”- MDN: CustomEvent by MDN Contributors — creating and dispatching custom DOM events
- MDN: dispatchEvent by MDN Contributors — dispatching events to the DOM
- mitt — Tiny functional event bus by Jason Miller — lightweight typed event emitter
- eventemitter3 — High-performance EventEmitter by Primus — fast EventEmitter implementation
- MDN: BroadcastChannel API by MDN Contributors — cross-window communication API