The Latest Ref Pattern stores the latest callback or value in a ref, allowing a long-lived function to access current data without being recreated on every render.
A callback can capture outdated values when it is created and then reused without updating its dependencies.
const feedPet = useCallback(() => {pet.eat(selectedPetFood);}, []); // Incorrect: captures initial values
Because the dependency array is empty, feedPet continues to reference the values captured by its original closure.
const petRef = useRef(pet);const foodRef = useRef(selectedPetFood);useEffect(() => {petRef.current = pet;foodRef.current = selectedPetFood;}, [pet, selectedPetFood]);const feedPet = useCallback(() => {petRef.current.eat(foodRef.current);}, []);
Now feedPet has a stable identity and reads the latest values stored in the refs.
The refs update after each committed render where the dependencies change.
A debounced function should retain its identity across renders while invoking the latest callback.
function useDebouncedCallback<T extends (...args: any[]) => void>(callback: T,delay: number) {const callbackRef = useRef(callback);useEffect(() => {callbackRef.current = callback;}, [callback]);const debounced = useMemo(() =>debounce((...args: Parameters<T>) => {callbackRef.current(...args);}, delay),[delay]);useEffect(() => {return () => debounced.cancel();}, [debounced]);return debounced;}
This example assumes debounce is a utility that supports .cancel(), such as Lodash’s implementation.
useMemo preserves the debounced function while delay remains unchanged.