custom hooks

useLatest( )

Explanation

The useLatest custom React hook is a powerful utility designed for managing dynamic values, especially complex ones like objects or functions. Its primary function is to ensure that the most recent value is always available without causing unnecessary re-renders in the component tree. This capability is particularly useful in scenarios where frequent updates occur, but re-rendering the entire component is undesirable, such as in event handlers or asynchronous operations.

This hook shines in cases where you need access to the latest value without affecting the rendering logic. By maintaining an up-to-date reference, useLatest enhances the performance of React applications by preventing unnecessary updates and streamlining the rendering process. This is particularly important when dealing with values that are recreated during parent component re-renders, especially when these values aren't memoized and are included in the dependency array of useEffect in a component or custom hook.

A key feature of useLatest is its ability to keep the reference to the latest value consistent, ensuring efficiency even in scenarios involving heavy computations or complex state management. However, developers should use useLatest with an understanding of its behavior: updates to the value stored in its .current property don't trigger re-renders in JSX. Proper comprehension and utilization of useLatest can lead to efficient management of dynamic values while avoiding potential rendering inefficiencies.

Usage Cases

  • Event Handlers: Keep the latest event handler function up-to-date without re-running useEffect on each render.
  • Asynchronous Operations: Ensure that asynchronous callbacks always use the latest value or function reference.
  • Optimizing Expensive useEffect Calls: Prevent useEffect from re-running unnecessarily when dependencies change frequently, especially when it's costly to execute the body of useEffect each time.
  • Maintaining References in Custom Hooks: Integrate seamlessly into custom hooks to maintain up-to-date references without extra re-renders.
  • Handling Dynamic Props: Ensure that props, especially those recreated on parent re-renders, are always current without causing additional renders.

Creation

Code implementation
1const useLatest = <T>(value: T): MutableRefObject<T> => {
2 // Create a `ref` that stores the latest value
3 const latestValue = useRef(value);
4
5 // `useLayoutEffect` is used here to guarantee that the value reference is updated
6 // before any subsequent renders or event handling take place.
7 // Since there's a possibility that when using `useEffect`, at a certain moment,
8 // the value might become outdated. This is because `useEffect` is asynchronous:
9 // its code executes after the browser finishes rendering. Consequently,
10 // the value will be updated after the browser's initial rendering.
11 // In contrast, `useLayoutEffect` is synchronous, ensuring that its code executes
12 // after all DOM mutations but before the browser updates the interface
13 useLayoutEffect(() => {
14 // Update `latestValue` when the `value` (its reference) changes
15 if (value) {
16 latestValue.current = value;
17 }
18 }, [value]);
19
20 // Returning a `ref` object instead of the actual value that is stored in `latestValue`.
21 // The `ref` object maintains the same reference, allowing to always access the latest value
22 // without causing re-renders, as `ref`s don't trigger re-renders when they're updated.
23 // Additionally, since `ref`s don't cause re-renders, they also don't trigger `useEffect`
24 // again if their value is used within it
25 return latestValue;
26};
27
28export default useLatest;
29

Reference

Code implementation
1const latestHandler = useRef(value);
2

Parameters

NameTypeDescription
valueTThe initial value to be stored and updated.

Return Values

NameTypeDescription
latestValueMutableRefObject<T>A mutable `ref` object that maintains the reference to the latest value provided to the hook.

Example Usages

Enhancing the useKeyPress Custom Hook

This example demonstrates how the useLatest enhances the useKeyPress custom hook by refining the logic for storing the optional handler function passed through arguments. This optimization promotes code cleanliness and modularity, ensuring that each hook focuses on its specific responsibility. Such architectural clarity facilitates better code maintenance and scalability.

For a full understanding of this custom hook, check out the useKeyPress article.

Before Using useLatest Hook

Code implementation
1export const useKeyPress = (
2 targetKey: string,
3 handler?: () => void
4): boolean => {
5 const [isKeyPressed, setIsKeyPressed] = useState(false);
6
7 // Create a `ref` that stores the latest `handler` function if provided
8 const latestHandler = useRef(handler ?? null);
9
10 // Update `latestHandler.current` when the handler changes.
11 // This ensures our `useEffect` below always gets the latest handler without
12 // needing to pass it in its dependencies array,
13 // which would cause the `useEffect` to re-run after every component render
14 // in case the `handler` is created from scratch inside it
15 useLayoutEffect(() => {
16 // Check if `handler` provided
17 if (handler) {
18 // Update `latestHandler` when the `handler` (its reference) changes
19 latestHandler.current = handler;
20 }
21 }, [handler]);
22
23 useEffect(() => {
24 const downHandler = ({ key }: KeyboardEvent) => {
25 if (key === targetKey) {
26 setIsKeyPressed(true);
27
28 // Call handler function if provided
29 if (latestHandler.current) {
30 latestHandler.current();
31 }
32 }
33 };
34
35 const upHandler = ({ key }: KeyboardEvent) => {
36 if (key === targetKey) {
37 setIsKeyPressed(false);
38 }
39 };
40
41 window.addEventListener('keydown', downHandler);
42 window.addEventListener('keyup', upHandler);
43
44 return () => {
45 window.removeEventListener('keydown', downHandler);
46 window.removeEventListener('keyup', upHandler);
47 };
48
49 // Although `latestHandler` is used within `useEffect`, it's not necessary
50 // to include it in the dependencies array. `useEffect` knows that the reference to `latestHandler`
51 // doesn't change, so it won't re-run unless `targetKey` changes or the component unmounts
52 }, [targetKey]);
53
54 return isKeyPressed;
55};
56

Using useLatest Hook

Code implementation
1export const useKeyPressWithHookInside = (
2 targetKey: string,
3 handler?: () => void
4): boolean => {
5 const [isKeyPressed, setIsKeyPressed] = useState(false);
6
7 // Use `useLatest` hook to obtain the reference to the latest `handler` function.
8 // The `handler` is conditionally passed into the custom hook
9 // since it's retrieved from the arguments and it's optional
10 const latestHandler = useLatest(handler ?? null);
11
12 useEffect(() => {
13 const downHandler = ({ key }: KeyboardEvent) => {
14 if (key === targetKey) {
15 setIsKeyPressed(true);
16
17 // Call handler function if provided
18 if (latestHandler.current) {
19 latestHandler.current();
20 }
21 }
22 };
23
24 const upHandler = ({ key }: KeyboardEvent) => {
25 if (key === targetKey) {
26 setIsKeyPressed(false);
27 }
28 };
29
30 window.addEventListener('keydown', downHandler);
31 window.addEventListener('keyup', upHandler);
32
33 return () => {
34 window.removeEventListener('keydown', downHandler);
35 window.removeEventListener('keyup', upHandler);
36 };
37
38 // Since `useEffect` is unaware that `latestHandler` is an object returned
39 // by `useRef` (which implies that its reference is immutable) within the
40 // custom hook `useLatest`, it needs to be listed in the dependencies array.
41 // Consequently, the `useEffect` body won't execute until `targetKey` changes
42 // or the component unmounts because the reference to `latestHandler` remains unchanged
43 }, [targetKey, latestHandler]);
44
45 return isKeyPressed;
46};
47

Enhancing the useOnClickOutside Custom Hook

This example illustrates how the useLatest optimizes the useOnClickOutside custom hook by managing the reference to the latest handler function. By integrating useLatest, the hook ensures that the handler function remains up-to-date without causing unnecessary re-renders. This refinement promotes cleaner code and maintains the hook's purpose of detecting clicks outside a specified element, enhancing code readability and maintainability.

For a full understanding of this custom hook, check out the useOnClickOutside article.

Before Using useLatest Hook

Code implementation
1export const useOnClickOutside = <T extends HTMLElement>(
2 element: RefObject<T>,
3 handler: () => void,
4 attached = true
5): void => {
6 // Create a `ref` that stores the latest `handler` function
7 const latestHandler = useRef(handler);
8
9 // Update `latestHandler.current` when the `handler` changes.
10 // This ensures our `useEffect` below always gets the latest handler without
11 // needing to pass it in its dependencies array,
12 // which would cause the `useEffect` to re-run after every component render
13 // in case the `handler` is created from scratch inside it
14 useLayoutEffect(() => {
15 // Update `latestHandler` when the `handler` (its reference) changes
16 latestHandler.current = handler;
17 }, [handler]);
18
19 useEffect(() => {
20 if (!attached || !element.current) return;
21
22 // Create an event listener that calls the handler function stored in the `ref`
23 const handleClick = (e: Event) => {
24 if (
25 latestHandler &&
26 element.current &&
27 !element.current.contains(e.target as Node)
28 ) {
29 latestHandler.current();
30 }
31 };
32
33 document.addEventListener('mousedown', handleClick);
34
35 return () => {
36 document.removeEventListener('mousedown', handleClick);
37 };
38
39 // Although `latestHandler` is used within `useEffect`, it's not necessary
40 // to include it in the dependencies array. `useEffect` knows that the reference to `latestHandler`
41 // doesn't change, so it won't re-run unless `element` or `attached` changes or the component unmounts
42 }, [element, attached]);
43};
44

Using useLatest Hook

Code implementation
1export const useOnClickOutsideWithHookInside = <T extends HTMLElement>(
2 element: RefObject<T>,
3 handler: () => void,
4 attached = true
5): void => {
6 // Use `useLatest` hook to obtain the reference to the latest `handler` function
7 const latestHandler = useLatest(handler);
8
9 useEffect(() => {
10 if (!attached || !element.current) return;
11
12 // Create an event listener that calls the handler function stored in the `ref`
13 const handleClick = (e: Event) => {
14 if (
15 latestHandler &&
16 element.current &&
17 !element.current.contains(e.target as Node)
18 ) {
19 latestHandler.current();
20 }
21 };
22
23 document.addEventListener('mousedown', handleClick);
24
25 return () => {
26 document.removeEventListener('mousedown', handleClick);
27 };
28
29 // Since `useEffect` is unaware that `latestHandler` is an object returned
30 // by `useRef` (which implies that its reference is immutable) within the
31 // custom hook `useLatest`, it needs to be listed in the dependencies array.
32 // Consequently, the `useEffect` body won't execute until `element` or `attached` changes
33 // or the component unmounts because the reference to `latestHandler` remains unchanged
34 }, [element, attached, latestHandler]);
35};
36

Following the same principle, part of the logic in two other custom hooks, for which we have separate articles — useEventListener and useIntersectionObserver, can also be replaced with useLatest.

Last updated on by @skrykateSuggest an improvement on Github repository.