Learn how to use refs to store the previous values of state and props in React since there React doesn’t have a built-in way to access this value.
Sometimes we want to access the value of a previous prop or state value. React does not provide a built-in way to do this, so let’s explore how to do this with refs.
In React, refs are non-reactive values in components and hooks. We can use this to store the values of things that we want to last between re-renders and won’t trigger a re-render when their values change.
To define a ref, we can use the useRef
hook.
For instance, we write:
import { useRef } from "react";
export default function App() {
const ref = useRef();
ref.current = 100;
console.log(ref.current);
return <div className="App"></div>;
}
to define the ref
ref with the React built in useRef
hook.
And we set its value by setting ref.current
to 100. We can put the ref.current
value assignment in the root level of the component since it won’t trigger a re-render of the component when we assign a value to it. And the value will be kept as long as the component is mounted.
These characteristics of refs make them good for keeping the previous values of React reactive values.
To use refs to store the previous values of states, we can assign those values to refs. For instance, we write:
import { useRef, useState } from "react";
export default function App() {
const prevCountRef = useRef();
const [count, setCount] = useState(0);
const onClick = () => {
prevCountRef.current = count;
setCount((c) => c + 1);
};
console.log(prevCountRef.current, count);
return (
<div className="App">
<button onClick={onClick}>increment</button>
<div>{count}</div>
</div>
);
}
to define the count
state with the useState
hook. And we define the prevCountRef
ref with the useRef
hook.
Next, we define the onClick
function that sets the prevCountRef
to the count
value before we call setCount
to increment the count
state value by 1.
Then we add a button to call onClick
when we click on the button to update both values. As a result, we see from the console log that prevCountRef.current
is always the previous count
value and count
having the latest value.
Likewise, we can store the previous values of props the same way since props are also reactive values.
To do the same thing with props, we write:
import { useEffect, useRef, useState } from "react";
const CountDisplay = ({ count }) => {
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
}, [count]);
console.log(prevCountRef.current, count);
return <div>{count}</div>;
};
export default function App() {
const [count, setCount] = useState(0);
const onClick = () => {
setCount((c) => c + 1);
};
return (
<div className="App">
<button onClick={onClick}>increment</button>
<CountDisplay count={count} />
</div>
);
}
to define the CountDisplay
component.
In it, we take the count
prop and assign it as the value of the prevCountRef
value as the count
value changes.
We can do that by putting prevCountRef.current = count;
in the useEffect
hook callback and make the hook watch the count
value.
This can keep the previous count
prop value since it won’t trigger a re-render until the count
prop gets updated.
As a result, we see from the console log that prevCountRef.current
is always the previous count
value and count
having the latest value as we do with the previous example.
To make the logic we have for storing previous props or states reusable, we can move the logic into its own hook.
To do this, we make the hook accept the reactive prop or state value and return the previous and latest reactive variable values.
For instance, we can write our own hook by writing:
import { useEffect, useRef, useState } from "react";
const usePrevious = (value) => {
const prevValueRef = useRef();
useEffect(() => {
prevValueRef.current = value;
}, [value]);
return { prev: prevValueRef.current, current: value };
};
export default function App() {
const [count, setCount] = useState(0);
const { prev, current } = usePrevious(count);
const onClick = () => {
setCount((c) => c + 1);
};
console.log(prev, current);
return (
<div className="App">
<button onClick={onClick}>increment</button>
<div>{count}</div>
</div>
);
}
to define the usePrevious
hook. In it, we just take the logic we previously had and put it in the hook function.
The only difference is that the hook takes the value
parameter where value
is the reactive value. And the hook function returns an object with the prev
and current
values that are derived from the same logic as before.
We use the usePrevious
hook by passing the count
state into it and take the prev
and current
properties from the object is returned.
Likewise, we can use the usePrevious
hook to store the previous value of props by writing:
import { useEffect, useRef, useState } from "react";
const usePrevious = (value) => {
const prevValueRef = useRef();
useEffect(() => {
prevValueRef.current = value;
}, [value]);
return { prev: prevValueRef.current, current: value };
};
const CountDisplay = ({ count }) => {
const { prev, current } = usePrevious(count);
console.log(prev, current);
return <div>{count}</div>;
};
export default function App() {
const [count, setCount] = useState(0);
const onClick = () => {
setCount((c) => c + 1);
};
return (
<div className="App">
<button onClick={onClick}>increment</button>
<CountDisplay count={count} />
</div>
);
}
We just call the usePrevious
with the count
prop in the CountDisplay
component instead of in the App
component.
Either way, we get the same result.
We can use refs to store the previous values of state and props since refs don’t trigger the re-rendering of components when their values change.
John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.