Let's start by asking ourselves why would we need the previous state? There might be some cases where you need to render a different view which also changes the current state based on the previous state.
Consider a simple example - you have an ON/OFF toggle button. Let's get a bit adventurous and not use a boolean state. Instead, we use the string representation ON
or OFF
. This example is only to simplify the process of how to get the previous state.
We start defining a simple component <Toggle />
which renders the button with the initial state as ON.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
const Toggle = () => {
const [buttonState, setButtonState] = useState("ON");
const handleClick = () => {
// our toggle logic will go here
}
return (
<div>
<button onClick={handleClick}>{buttonState}</button>
</div>
)
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
Here comes the key part. To get the previous state, we use the useRef()
hook in React.
What's the use of useRef()
here?
useRef()
is a really powerful hook in React which provides a ref which stores a value that is similar to a state. Refs can have other use cases as well. This is just one of them. The main difference being, React does not re-render the UI if the ref's value changes unlike when a state's value changes.
We use a custom usePrevious()
hook that would give us the previous value. The ref contains a .current
property that holds the value. We will look into how it works down below.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
// hook that would return the ref to the previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref;
}
const Toggle = () => {
// component logic..
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
Finally, adding our toggle logic inside the handleClick function. We'll also render the previous state on the UI to see it clearly.
import React, { useState, useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref;
}
const Toggle = () => {
const [buttonState, setButtonState] = useState("ON");
// getting the value of the previous state
const previousState = usePrevious(buttonState);
const handleClick = () => {
// toggling logic
if(previousState.current === "ON")
setButtonState("OFF");
else
setButtonState("ON");
}
return (
<div>
<button onClick={handleClick}>{buttonState}</button>
<!-- we also render the previous state to see what changes happen -->
<p>Previous State: {previousState.current}</p>
</div>
)
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
How does this work?
During the first render when the usePrevious()
custom hook is called, the useEffect
does not run the () => ref.current = value
until the first render finishes. So the hook returns undefined as the ref's value initially. Only during the next re-render, theuseEffect
runs with the value that was passed to it in the previous re-render(i.e. "ON" from the first render) which enables it to store the previous value.
So this is what happens rendering in order,
- state = "ON", ref = undefined
- state = "OFF", ref = "ON" (value of ref comes from the previous render)
- state = "ON", ref = "OFF" and so on..
Hope you understood how you can use refs to get the previous state value. Share your comments and feedback down below! ๐
Ciao! ๐