How to Track Previous State in React
Tags: react, hooks
Published: 2023-11-07 08:44:56

---
title: How to Track Previous State in React
published: true
description: Explaining how to track previous state in React using refs and effects, optimising to use only state, then extracting the logic to a custom hook
tags: react, hooks
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-11-05 06:31 +0000
---
When developing React applications, we may need to keep track of both the current and previous state. This article discusses various techniques for achieving this, and how to encapsulate the logic into reusable custom hooks.

To demonstrate the examples, we'll use a basic application that displays a counter, its previous value and a button to increment it:

{% embed https://codesandbox.io/embed/ecstatic-khayyam-6gk3mw?fontsize=14&hidenavigation=1&theme=dark %}

Let's explore how to build this component.

## Approach 1: Use Refs and Effects
A [common approach](https://levelup.gitconnected.com/how-to-get-previous-state-in-react-6bce387b32d6) is to use a [ref](https://react.dev/learn/referencing-values-with-refs) to store the previous state:

```jsx
function App() {
  const [counter, setCounter] = useState(0);
  const ref = useRef();

  useEffect(() => {
    ref.current = counter;
  }, [counter]);

  return (
    <div>
      <div>Counter: {counter}</div>
      <div>Previous Counter: {ref.current}</div>
      <button onClick={() => setCounter(counter + 1)}>
        Increment counter
       </button>
    </div>
  );
}
```

An [effect](https://react.dev/reference/react/useEffect) monitors `counter` and updates `ref.current` when it changes. Changing a ref's value won't trigger a re-render, so the element displaying `ref.current` shows the previous value of `counter` until the next render (i.e. when `counter` changes).

![Tracking counter's previous state using a ref](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2t8dhbxry6j1lf4m09j.gif)

This approach would work fine when the only state variable in our component is `counter`, but once there are multiple state variables (as most components have), a re-render that isn't triggered by `counter` would synchronise the elements displaying  `counter` and `ref.current`, displaying the same value for both.

To see this in action, let's add a new state variable, `title`:

```jsx
// ...
const [counter, setCounter] = useState(0);
/* 👇 add title here */
const [title, setTitle] = useState("");
// ...
```

Add an input element to display and update `title`:

```jsx
{/* ... */}
<div className="App">
{/* 👇 add the input here */}
  <input
    value={title}
    onChange={(e) => setTitle(e.target.value)}
  />
{/* ... */}
```
When text is enter in the title input (triggering a re-render), the values displayed for Counter and Previous Counter become the same:

![Counter and Previous Counter are the same when title is updated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2p3zr3vq4o7kbwb4is4l.gif)

In general, you want to avoid using refs for values that will be displayed, as stated in the [React docs](https://react.dev/reference/react/useRef#usage):

> Changing a ref does not trigger a re-render, so refs are not appropriate for storing information you want to display on the screen. Use state for that instead.

## Approach 2: Use the state setter function
To fix the state-ref synchronisation bug, [another approach](https://www.geeksforgeeks.org/how-to-get-previous-state-in-reactjs-functional-component/) is to use a new state variable         `previousCounter` to keep track of `counter`'s previous value:

```jsx
function App() {
  const [counter, setCounter] = useState(0);
  /* 👇 add previousCounter here */
  const [previousCounter, setPreviousCounter] = useState(null);
  const [title, setTitle] = useState("");

  /* 👇 add an event handler for the increment button */
  const handleIncrementButtonClick = () => {
    setCounter((counter) => {
      setPreviousCounter(counter);
      return counter + 1;
    });
  };

  return (
    <div className="App">
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <div>Counter: {counter}</div>
      <div>Previous Counter: {previousCounter}</div>
      {/* 👇 update the button's onclick handler */}
      <button onClick={handleIncrementButtonClick}>Increment counter</button>
    </div>
  );
}
```

The value of `previousCounter` is updated when setting `counter` in the click event handler for the increment button. Now Counter and Previous Counter remain synchronised even when the title is updated:

![Counter and Previous Counter remain synchronised even when the title is updated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7on1dt5mkxisaoysdvh4.gif).

With this approach, we've eliminated the need for using both refs and effects. Effects are considered an [escape hatch](https://react.dev/learn/you-might-not-need-an-effect), so replacing them with state wherever possible makes our code more stable. Now we'll see how to extract this logic into a custom hook.

## Approach 3: Use two state variables to track current and previous values

In this approach (adapted from the [usehooks package](https://github.com/uidotdev/usehooks)), we'll define a state variable `currentCounter` to track `counter`'s value:

```jsx
function App() {
  const [counter, setCounter] = useState(0);
  /* 👇 add currentCounter here */
  const [currentCounter, setCurrentCounter] = useState(counter);
  const [previousCounter, setPreviousCounter] = useState(null);
  const [title, setTitle] = useState("");

  /* 👇 conditionally update previousCounter and currentCounter  */
  if (counter !== currentCounter) {
    setPreviousCounter(currentCounter);
    setCurrentCounter(counter);
  }

  /* 👇 update event handler */
  const handleIncrementButtonClick = () => {
    setCounter(counter + 1);
  };

  return (
    <div className="App">
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <div>Counter: {counter}</div>
      <div>Previous Counter: {previousCounter}</div>
      <button onClick={handleIncrementButtonClick}>Increment counter</button>
    </div>
  );
}
```

The initial value of `currentCounter` is set to match `counter`. On subsequent re-renders, when `currentCounter` and `counter` don't match, that means `counter` has been updated and `currentCounter` now holds its previous value. We set `previousCounter` to `currentCounter`'s value, and update `currentCounter` to match `counter`. Now the `handleIncrementButtonClick` event handler only needs to increment `counter`.

Both state and props can be tracked using this method. The logic easily be extracted into a custom hook:

```javascript
function usePrevious(value) {
  const [current, setCurrent] = useState(value);
  const [previous, setPrevious] = useState(null);

  if (value !== current) {
    setPrevious(current);
    setCurrent(value);
  }

  return previous;
}
```

To use this hook, we pass it the state or prop we want to track:

```javascript
function App() {
  const [counter, setCounter] = useState(0);
  /* 👇 this is all we need now */
  const previousCounter = usePrevious(counter);
  const [title, setTitle] = useState("");

  const handleIncrementButtonClick = () => {
    setCounter(counter + 1);
  };

  return (
    <div className="App">
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <div>Counter: {counter}</div>
      <div>Previous Counter: {previousCounter}</div>
      <button onClick={handleIncrementButtonClick}>Increment counter</button>
    </div>
  );
}
```

## Approach 4: Use a custom hook to set both current and previous values

For tracking a value within a component's state (this won't work for props), we can use a custom hook that returns a state variable, its setter and another state variable to track its previous state:

```javascript
function usePreviousStateTracking (initialValue) {
  const [current, setCurrent] = useState(initialValue);
  const [previous, setPrevious] = useState(null);

  function setPreviousAndCurrent(nextValue) {
    setPrevious(current)
    setCurrent(nextValue)
  }

  return [current, setPreviousAndCurrent, previous];
}
```

The hook can be used like this:

```jsx
export default function App() {
  /* 👇 this is all we need now */
  const [counter, setCounter, previousCounter] = usePreviousStateTracking(0);
  const [title, setTitle] = useState("");

  const handleIncrementButtonClick = () => {
    setCounter(counter + 1);
  };

  return (
    <div className="App">
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <div>Counter: {counter}</div>
      <div>Previous Counter: {previousCounter}</div>
      <button onClick={handleIncrementButtonClick}>Increment counter</button>
    </div>
  );
}
```

In some cases, you might want to use this approach to skip the extra re-render from approaches 2 & 3 (caused by setting the previous state value separately from the current one). It's worth noting that when an additional re-render is causing notable performance issues, it may indicate that other optimisations need to be made in your component.

## Conclusion

In this article, we explored various approaches to track previous state in React components. We started by solving the problem using refs and effects, then discussed different approaches that use only state. Finally, we used custom hooks to encapsulate the logic into reusable functions.

If you're interested in seeing how to track multiple versions of a state variable, be sure to leave a comment below.