/** * @file Hook for batching multiple state updates to optimize rendering performance */ import * as React from "react"; type UpdateFunction = (prevState: T) => T; type BatchedUpdate = T | UpdateFunction; type BatchedUpdatesOptions = { delay?: number; maxBatchSize?: number; }; /** * Hook for batching multiple state updates to reduce re-renders * Uses requestAnimationFrame for optimal performance */ export const useBatchedUpdates = ( initialState: T, options: BatchedUpdatesOptions = {}, ): [T, (update: BatchedUpdate) => void, () => void] => { const { delay = 0, maxBatchSize = 100 } = options; const [state, setState] = React.useState(initialState); const pendingUpdates = React.useRef>>([]); const frameRef = React.useRef(null); const timeoutRef = React.useRef(null); // Apply all pending updates const flushUpdates = React.useCallback(() => { if (pendingUpdates.current.length === 0) { return; } setState((prevState) => { let newState = prevState; for (const update of pendingUpdates.current) { newState = update(newState); } return newState; }); pendingUpdates.current = []; frameRef.current = null; if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } }, []); // Schedule update batch const scheduleUpdate = React.useCallback( (update: BatchedUpdate) => { const updateFn: UpdateFunction = typeof update === "function" ? (update as UpdateFunction) : (_prevState: T) => update; pendingUpdates.current.push(updateFn); // If we've reached max batch size, flush immediately if (pendingUpdates.current.length >= maxBatchSize) { flushUpdates(); return; } // Cancel any existing scheduled flush if (frameRef.current !== null) { cancelAnimationFrame(frameRef.current); } if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); } // Schedule flush if (delay > 0) { // Use timeout for delayed batching timeoutRef.current = setTimeout(() => { frameRef.current = requestAnimationFrame(flushUpdates); }, delay); } else { // Use next animation frame for immediate batching frameRef.current = requestAnimationFrame(flushUpdates); } }, [flushUpdates, delay, maxBatchSize], ); // Force flush all pending updates const forceFlush = React.useCallback(() => { if (frameRef.current !== null) { cancelAnimationFrame(frameRef.current); } if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); } flushUpdates(); }, [flushUpdates]); // Cleanup on unmount React.useEffect(() => { return () => { if (frameRef.current !== null) { cancelAnimationFrame(frameRef.current); } if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); } }; }, []); return [state, scheduleUpdate, forceFlush]; };