How to Store Previous Values with useRef

Track the previous value of a prop or state variable with a useRef plus Effect pattern, and learn when the official render-time alternative is better.

6 min read

Some UI needs to know how a value changed, not just what it is now. A "moved up" or "moved down" label compares the current value with the one before it, and that previous value has to come from somewhere. A ref paired with an Effect is a lightweight way to store previous values so you can compare them.

The usePrevious pattern

The idea is simple: keep the old value in a ref, and copy the new value into the ref after every render.

App.jsxApp.jsx
import { useRef, useEffect } from "react";
 
function usePrevious(value) {
  const ref = useRef();
 
  useEffect(() => {
    ref.current = value;
  }, [value]);
 
  return ref.current;
}

During a render, the Effect has not run yet, so ref.current still holds the value from the previous render. The function returns that stale value on purpose.

After React commits, the Effect runs and stores the new value for next time. The first render returns undefined because nothing came before it.

Show a trend as a counter changes

A counter can display whether the number went up or down since the last change, using the usePrevious helper defined above.

App.jsxApp.jsx
import { useState } from "react";
 
function TrendCounter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  const trend = prevCount === undefined ? null : count > prevCount ? "up" : count < prevCount ? "down" : "same";

The hook returns the value from the previous render, which is exactly what the component needs. The trend then compares the fresh count with that older value.

App.jsxApp.jsx
  return (
    <>
      <p>Count: {count}</p>
      {trend && <p>Moved {trend}</p>}
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
    </>
  );
}

After the first click, the label shows whether the count moved up or down. On the first render there is no previous value, so no trend appears. Each later render compares the fresh count with prevCount, which is one render behind by design.

Why the update belongs in an Effect

Writing ref.current = value during render would break the rules of pure rendering, because render output would depend on a write that happens partway through. Moving the write into an Effect keeps render pure and guarantees the ref holds the value of the last completed render.

The Effect has no cleanup because it stores a value rather than starting a subscription or timer. The [value] dependency ensures the ref is refreshed whenever the tracked value changes.

When this pattern is the wrong tool

The ref version is fine for comparing values inside Effects and event handlers. It is awkward when the previous value must drive the screen, because the returned value lags one render behind.

React's recommended alternative

For UI that must render based on the previous value, the React docs recommend a render-time setState pattern instead of an Effect. It updates the previous value immediately after the render exits, so children do not render twice and the screen never shows stale data.

The core tradeoff is the same one covered in useRef vs useState: reach for the ref when nothing visible depends on it.

Common mistakes

  • Reading or writing ref.current during render instead of in an Effect.
  • Forgetting the dependency array, so the ref is updated on every render even when the value did not change.
  • Expecting the first returned value to equal the initial value. The first call returns undefined.
  • Using the ref value to render UI, which leads to a one-render lag.

What to learn next

The ref toolkit grows from here. See how to access and measure DOM elements to apply refs to real browser layout.

Rune AI

Rune AI

Key Insights

  • Store the old value in a ref, then update it in an Effect after each render.
  • The returned value is always one render behind the current value.
  • Use the pattern for comparisons, not for driving the screen.
  • React's render-time setState pattern is the official alternative for UI-driving cases.
  • Never write ref.current during render except for lazy initialization.
RunePowered by Rune AI

Frequently Asked Questions

Why update the ref inside an Effect?

The Effect runs after React commits the DOM, so it records the value after the render has finished. Updating the ref during render would make the output unpredictable.

Why is the previous value one render behind?

The ref stores the value from the last completed render. During the current render, ref.current still holds the value from the previous one, which is exactly what you want.

Is there an official React alternative?

Yes. React's docs recommend a render-time setState pattern for cases where you must render based on the previous value, because it avoids the one-render lag.

Conclusion

A useRef plus Effect records the previous value of a prop or state variable without triggering extra renders. Use it for comparisons in Effects and handlers, and switch to the render-time setState pattern when the previous value drives the UI.