custom hooks

usePrevious( )

The code provided in this article is simplified as much as possible to avoid overwhelming the reader with code and to convey the essence of the problem and its solution. In interactive examples, heading tags (`h1...h6`) are replaced with span tags to prevent potential conflicts with the main page structure.

Explanation

The custom React hook usePrevious offers a straightforward solution for tracking the previous value of a state or prop within functional components. With just a few lines of code, developers can easily access the previous value without resorting to complex state management techniques.

By leveraging React's useRef and useEffect hooks, usePrevious maintains a reference to the previous value, updating it whenever the provided value changes. This approach ensures that the previous value remains accessible across renders, providing a simple yet effective method for comparison and tracking.

Integrating usePrevious into React components enhances their flexibility and facilitates scenarios where knowing the previous state or prop value is essential. Whether managing UI transitions, handling asynchronous updates, or implementing conditional logic based on changes, usePrevious empowers developers with a reliable tool for building robust and responsive React applications.

Usage Cases

  • Tracking State Changes: Monitor and compare changes in state values, facilitating dynamic UI updates and conditional rendering based on state transitions.
  • Monitoring Value Changes: Observe alterations in specific values, enabling responsive behavior in React components and enhancing user interaction through visual feedback.
  • Conditional Rendering: Enhance conditional rendering logic by comparing current and previous values, enabling efficient rendering of components based on state or prop changes.
  • Managing Color Transitions: Facilitate smooth transitions between different color states in React components, improving visual aesthetics.
  • Handling Asynchronous Operations: Efficiently manage asynchronous operations by tracking previous values, ensuring consistent user experience.
  • Enhancing User Interaction: Provide insights into previous state or prop values, enabling intuitive UI updates and seamless transitions between different application states.

Creation

Code implementation
1const usePrevious = <T>(value: T): T | null => {
2 // Call `useRef` to create a mutable `ref` object that persists across renders
3 const prevValueRef = useRef<T | null>(null);
4
5 useEffect(() => {
6 // Update `prevValueRef` when the `value` (its reference) changes
7 prevValueRef.current = value;
8 }, [value]);
9
10 return prevValueRef.current;
11};
12
13export default usePrevious;
14

Reference

Code implementation
1const prevValue = usePrevious(value);
2

Parameters

NameTypeDescription
valueTThe current value to track and compare.

Return Values

NameTypeDescription
prevValueT | nullThe previous value of the provided value parameter.

Example Usages

Tracking Count Changes

This example demonstrates how to employ the usePrevious custom hook to track and display the previous value of a state in a React functional component. By integrating usePrevious, developers can easily compare the current and previous states, enhancing the user experience by providing insight into state transitions. The rendered UI showcases both the current and previous count values, enabling users to observe changes as they interact with the component.

Code implementation
1const Container: FC = () => {
2 // State to track the current count
3 const [count, setCount] = useState(1);
4
5 // Use the custom hook to get the previous value of the `count` state
6 const prevCount = usePrevious(count);
7
8 const prevValue = prevCount !== null ? prevCount : 'null';
9
10 return (
11 <Layer title="Container">
12 <Text>
13 Current value: {count}
14 <br />
15 Previous value: {prevValue}
16 </Text>
17 <Counter
18 min={1}
19 max={9}
20 value={count}
21 onChange={setCount}
22 />
23 </Layer>
24 );
25};
26
An interactive output of the code above

Conditional Emoji Display Based on Value Difference

This example demonstrates the usage of a custom usePrevious hook to access the previous state value. A random value is generated and stored in the state on button click, and the previous value is retrieved using the hook. The component calculates whether the difference between the current and previous values is even and conditionally renders an emoji if isDifferenceEven is true. This demonstrates the versatility of usePrevious in tracking state transitions and enhancing user interaction within React components.

Code implementation
1const Container: FC = () => {
2 // State to manage the current value
3 const [value, setValue] = useState(0);
4
5 // Use `usePrevious` hook to get the previous value of `value`
6 const prevValue = usePrevious(value);
7
8 // Function to handle button click and generate a random value
9 const handleClick = () => {
10 const randomValue = generateRandomValue();
11
12 setValue(randomValue);
13 };
14
15 // Checking if the difference between current and previous value is even
16 const isDifferenceEven = prevValue ? (value - prevValue) % 2 === 0 : null;
17
18 return (
19 <Layer title="Container">
20 <Message>If the difference is even, the Emoji will appear.</Message>
21 <Text>
22 Current Value: {value}
23 <br />
24 Previous Value: {prevValue !== null ? prevValue : 'null'}
25 <br />
26 {/* Conditionally render emoji when the difference is even */}
27 Difference even: {isDifferenceEven ? 'Yes 😊!' : 'No'}
28 </Text>
29 <div className="flex w-full justify-center">
30 <Button onClick={handleClick}>Generate random value</Button>
31 </div>
32 </Layer>
33 );
34};
35
An interactive output of the code above

Color Change with Previous Value Display

This component tracks the current and previous color values. It generates a random color when the button is clicked, updating the currentColor state. The component then conditionally displays both the current and previous colors, with the previous color defaulting to the current color if it isn't yet available.

Code implementation
1const Container: FC = () => {
2 // State to manage the current color
3 const [currentColor, setCurrentColor] = useState('hsl(303, 70%, 100%)');
4
5 // Call `usePrevious` hook to get the previous value of `currentColor`
6 const prevColor = usePrevious(currentColor);
7
8 // Function to handle button click and generate a random color
9 const handleClick = () => {
10 const randomColor = getRandomColor();
11
12 setCurrentColor(randomColor);
13 };
14
15 return (
16 <Layer title="Container">
17 <Text>
18 Current:{' '}
19 <span style={{ color: currentColor }}>{currentColor}</span>
20 <br />
21 Previous:{' '}
22 <span style={{ color: prevColor ?? 'white' }}>
23 {prevColor ?? currentColor}
24 </span>
25 </Text>
26 <div className="flex w-full justify-center">
27 <Button onClick={handleClick}>Generate random color</Button>
28 </div>
29 </Layer>
30 );
31};
32
An interactive output of the code above

User Name Fetching with Previous Value Tracking

This example demonstrates how to utilize the usePrevious custom hook to track changes in a user's name fetched asynchronously. By integrating usePrevious, developers can efficiently compare the current and previous name values, providing users with insight into name transitions. The usage of the usePrevious hook remains effective even in scenarios involving asynchronous operations. The rendered UI showcases both the current and previous names, enhancing the user experience by visualizing updates in real-time.

Code implementation
1const idsList = Array.from({ length: 5 }, (_, index) => ({
2 id: index + 1,
3 name: `User Id: ${index + 1}`,
4}));
5
6// Function to fetch user's name asynchronously
7const fetchUserName = async (personId: number) => {
8 const data = await loadData();
9
10 const user = data.find((user) => user.id === personId);
11
12 return user!.name;
13};
14
15const Container: FC = () => {
16 // State to manage selected person ID
17 const [personId, setPersonId] = useState(1);
18
19 // State to manage fetched name
20 const [value, setValue] = useState<string | null>(null);
21
22 // State to manage loading state
23 const [isLoading, setLoading] = useState(true);
24
25 // Use `usePrevious` hook to get the previous name
26 const prevValue = usePrevious(value);
27
28 // Handler to update the selected person ID
29 const handleChange = (item: (typeof idsList)[0]) => {
30 setPersonId(item.id);
31 };
32
33 useEffect(() => {
34 let isCancelled = false;
35
36 setLoading(true);
37
38 const fetchName = async () => {
39 try {
40 const newName = await fetchUserName(personId);
41
42 // Check if the component is unmounted to avoid updating state after unmounting
43 if (!isCancelled) {
44 setValue(newName);
45
46 setLoading(false);
47 }
48 } catch (error) {
49 console.error('Error fetching name:', error);
50
51 setLoading(false);
52 }
53 };
54
55 fetchName();
56
57 return () => {
58 // Set a flag to handle the case where the component unmounts before the request completes
59 isCancelled = true;
60 };
61 }, [personId]);
62
63 const currentName = value ?? 'null';
64
65 const prevName = prevValue ?? 'null';
66
67 return (
68 <Layer title="Container">
69 <label>Select a person ID:</label>
70 <List
71 items={idsList}
72 selected={personId}
73 onClick={handleChange}
74 />
75 <Text>
76 <span className={isLoading ? 'opacity-30' : ''}>
77 Current name: {currentName}
78 <br />
79 Previous name: {prevName}
80 </span>
81 </Text>
82 </Layer>
83 );
84};
85
An interactive output of the code above
Last updated on by @skrykateSuggest an improvement on Github repository.