feat: introduce core-events package with event bus, hooks, and a web showcase demo
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import type { AppEvents } from './events.registry';
|
||||
import { eventBus, publish as busPublish } from './event-bus';
|
||||
|
||||
// ─── useAppEvent ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Subscribe to an application event with automatic cleanup on unmount.
|
||||
*
|
||||
* The handler is stored in a ref so that:
|
||||
* 1. The subscription is stable — re-renders don't cause unsubscribe/resubscribe churn.
|
||||
* 2. The handler always sees the latest closure values (no stale closures).
|
||||
* 3. The parent component's render cycle is never triggered by the subscription itself.
|
||||
*
|
||||
* @param type - The event name from `AppEvents`.
|
||||
* @param handler - Callback receiving the typed payload. May be updated on re-render.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* useAppEvent('AUTH:PROFILE_UPDATED', (profile) => {
|
||||
* console.log(profile.name); // fully typed, auto-cleaned on unmount
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useAppEvent<K extends keyof AppEvents>(
|
||||
type: K,
|
||||
handler: (event: AppEvents[K]) => void,
|
||||
): void {
|
||||
// Always keep the latest handler in a ref to avoid stale closures
|
||||
// and prevent re-subscription on every render.
|
||||
const handlerRef = useRef(handler);
|
||||
handlerRef.current = handler;
|
||||
|
||||
useEffect(() => {
|
||||
// Create a stable delegate that forwards to the latest handler ref
|
||||
const delegate = (event: AppEvents[K]) => {
|
||||
handlerRef.current(event);
|
||||
};
|
||||
|
||||
eventBus.on(type, delegate);
|
||||
|
||||
// Cleanup: unsubscribe when the component unmounts or `type` changes.
|
||||
// This is the critical memory-leak prevention mechanism.
|
||||
return () => {
|
||||
eventBus.off(type, delegate);
|
||||
};
|
||||
}, [type]);
|
||||
}
|
||||
|
||||
// ─── usePublishEvent ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns a strictly typed `publish` function.
|
||||
*
|
||||
* The returned function is referentially stable (memoized) so it
|
||||
* can be safely passed as a prop or used in dependency arrays
|
||||
* without causing unnecessary re-renders.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const publish = usePublishEvent();
|
||||
*
|
||||
* const handleClick = () => {
|
||||
* publish('DEVICE:PRINT_RECEIPT', {
|
||||
* receiptId: '001',
|
||||
* items: [{ name: 'Widget', qty: 2, price: 9.99 }],
|
||||
* total: 19.98,
|
||||
* cashierName: 'Firman',
|
||||
* timestamp: Date.now(),
|
||||
* });
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export function usePublishEvent(): typeof busPublish {
|
||||
return useCallback(busPublish, []);
|
||||
}
|
||||
Reference in New Issue
Block a user