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(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, []); }