@winglet/react-utils
Version:
React utility library providing custom hooks, higher-order components (HOCs), and utility functions to enhance React application development with improved reusability and functionality
74 lines (73 loc) • 3.22 kB
TypeScript
/**
* Creates a stable callback reference that always invokes the latest version of the provided handler.
*
* This hook solves the common "stale closure" problem in React by maintaining a stable
* function reference while ensuring it always calls the most recent version of your handler.
* It combines the benefits of `useCallback` (stable reference) with the flexibility of
* always having access to the latest props and state.
*
* ### Problem it Solves
* In React, callbacks passed to child components or used in effects often capture values
* from their closure. When these values change, the callback becomes "stale" and operates
* on outdated data. This hook ensures your callback always uses current values without
* triggering re-renders in memoized components.
*
* ### Use Cases
* - **Event Handlers in Memoized Components**: Pass stable callbacks without breaking memoization
* - **Timer/Interval Callbacks**: Access latest state without recreating timers
* - **External Library Integration**: Provide callbacks that need current component state
* - **WebSocket/EventSource Handlers**: Handle real-time events with current state
* - **Debounced/Throttled Functions**: Maintain stable references while using fresh data
*
* ### Key Benefits
* - Prevents unnecessary re-renders in child components
* - Eliminates the need to include all dependencies in useCallback
* - Avoids the complexity of managing refs manually
* - Works seamlessly with React.memo and useMemo optimizations
*
* @example
* ```typescript
* // Problem: Stale closure in interval
* const [count, setCount] = useState(0);
* useEffect(() => {
* const id = setInterval(() => {
* console.log(count); // Always logs initial value
* }, 1000);
* return () => clearInterval(id);
* }, []); // Empty deps = stale closure
*
* // Solution: Using useHandle
* const [count, setCount] = useState(0);
* const logCount = useHandle(() => {
* console.log(count); // Always logs current value
* });
* useEffect(() => {
* const id = setInterval(logCount, 1000);
* return () => clearInterval(id);
* }, [logCount]); // logCount reference never changes
*
* // With memoized child components
* const ExpensiveChild = React.memo(({ onClick }) => { ... });
* const Parent = () => {
* const [data, setData] = useState(initialData);
*
* // This would cause re-renders:
* // const handleClick = useCallback(() => process(data), [data]);
*
* // This maintains stable reference:
* const handleClick = useHandle(() => process(data));
*
* return <ExpensiveChild onClick={handleClick} />;
* };
*
* // Optional handler support
* const safeHandler = useHandle(props.onComplete);
* // If props.onComplete is undefined, safeHandler returns null instead of throwing
* ```
*
* @typeParam P - The array type of the handler's parameters
* @typeParam R - The return type of the handler
* @param handler - Optional function to be wrapped. If undefined, the returned function will return null
* @returns A stable callback that always invokes the latest version of the handler
*/
export declare const useHandle: <P extends Array<any>, R>(handler?: (...args: P) => R) => ((...args: P) => R);