UNPKG

@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

82 lines (81 loc) 3.28 kB
/** * Creates a ref that always contains the current value, updating automatically on every render. * * This hook solves the "stale closure" problem by ensuring that async callbacks, effects, * and event handlers always have access to the latest value without needing to recreate * themselves. Unlike a regular `useRef`, this ref's current value is updated on every * render to match the provided value. * * ### Problem it Solves * When callbacks are created in React, they capture values from their closure. If these * values change later, the callback still sees the old values. This hook ensures you * always have access to the current value without recreating callbacks. * * ### Use Cases * - **Async Callbacks**: Access current state in setTimeout/setInterval callbacks * - **Event Handlers**: Use latest props/state in debounced or throttled handlers * - **Effect Cleanup**: Access current values in cleanup functions * - **Imperative Handles**: Expose methods that use current component state * - **External Library Callbacks**: Provide callbacks to non-React code * * ### Comparison with Alternatives * - **vs useRef**: This updates automatically; useRef requires manual updates * - **vs useState**: This doesn't trigger re-renders when updated * - **vs useCallback deps**: This avoids recreating callbacks when values change * * @example * ```typescript * // Problem: Stale state in async callback * const [count, setCount] = useState(0); * const handleAsync = useCallback(async () => { * await delay(1000); * console.log(count); // Always logs 0, even if count changed * }, []); // Can't add count to deps or callback recreates * * // Solution: Using useReference * const [count, setCount] = useState(0); * const countRef = useReference(count); * const handleAsync = useCallback(async () => { * await delay(1000); * console.log(countRef.current); // Always logs current count * }, [countRef]); // countRef never changes, so callback is stable * * // Access current props in cleanup * const connectionRef = useReference(props.connection); * useEffect(() => { * const ws = new WebSocket(url); * * return () => { * // Access current connection state during cleanup * if (connectionRef.current.shouldNotify) { * notifyDisconnection(); * } * ws.close(); * }; * }, [url, connectionRef]); * * // Debounced handler with current values * const searchTerm = useReference(inputValue); * const debouncedSearch = useMemo( * () => debounce(() => { * // Always searches with current term * search(searchTerm.current); * }, 500), * [searchTerm] * ); * * // Imperative handle with current state * const [items, setItems] = useState([]); * const itemsRef = useReference(items); * * useImperativeHandle(ref, () => ({ * getItemCount: () => itemsRef.current.length, * hasItems: () => itemsRef.current.length > 0, * }), [itemsRef]); * ``` * * @typeParam T - The type of the value to keep current reference to * @param value - The value to track. The ref will always contain this latest value * @returns A ref object whose `current` property always equals the latest `value` */ export declare const useReference: <T>(value: T) => import("react").RefObject<T>;