skip to content

Implement a React `usePrevious(value)` hook that returns the value this component had on its previous render. Where must the ref be written, and what does the hook return the first time it runs?

level: middleimportance: should knowfreq 46%

answer

  1. remember without re-rendering
  2. order of read and write
  3. no dependency array on purpose
  4. render must stay pure
  5. the very first call has nothing yet

basics

~20 s

Store the value in a useRef and overwrite it inside a useEffect that runs after every render, returning ref.current first. Reading before the effect writes yields the previous render's value; on the first render it is undefined.

solid answer

~40 s

The hook is a ref plus an effect: `const ref = useRef(undefined)`, then `useEffect(() => { ref.current = value })` with no dependency array, and `return ref.current`. The ordering is the whole trick — the component body returns `ref.current` while it still holds the value written by the *previous* commit, and the effect then overwrites it for next time. Writing `ref.current = value` directly in the body would return the current value instead, and mutating a ref during render is also unsafe under concurrent rendering because a render can be thrown away or replayed. On the first render there is nothing to have been written yet, so the hook returns `undefined`, which callers must handle — typing it `T | undefined` is more honest than pretending the previous value always exists.

code

typescript · 9 lines
typescript
import { useRef, useEffect } from 'react';

export function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T | undefined>(undefined);
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

go deeper

for a junior

Know that useRef gives you a mutable box that survives re-renders and never triggers one, and be able to write the three-line hook with the write in an effect and the read as the return value.

for a middle

Explain the render → commit → effect ordering that makes the returned value the previous one, why the effect deliberately has no dependency array, and why the first render must report undefined.

for a senior

Argue purity: an in-body mutation misbehaves under StrictMode double-invocation and abandoned concurrent renders. Also push back on misuse — resetting state on an identity change belongs to key, and derived values should be computed during render, not diffed.

for a principal

Own when a shared usePrevious in a component library becomes a smell: widespread previous-value diffing usually signals state that should have been derived, keyed, or moved. Weigh the render-time variant's immediacy against its purity cost as a codebase-level standard.

## The implementation ```js import { useRef, useEffect } from 'react'; function usePrevious(value) { const ref = useRef(undefined); useEffect(() => { ref.current = value; }); return ref.current; } ``` Three details carry the whole answer: the storage is a ref, the write is in an effect, and the read happens before the write. ## Why a ref and not state The previous value is something you want to *remember* across renders without causing one. Putting it in `useState` and setting it during or after render creates a second render for every update — at best wasteful, at worst an infinite loop, since setting the previous value is itself a change that triggers another render. A ref is a mutable box React keeps attached to the component instance across renders and never watches; writing to it has no effect on rendering at all. That is exactly the semantics wanted here: remember, do not react. ## Why the write goes in an effect Render in React is expected to be pure: given the same props and state, the component body should produce the same output and touch nothing outside itself. React relies on that — under concurrent rendering a render can be started, abandoned, and started again, and in development StrictMode deliberately double-invokes component bodies to surface impurity. A `ref.current = value` line sitting in the body is a side effect during render: run it twice and the "previous" value silently becomes the current one. Effects run after the commit, once React has finished putting the result on screen. So the sequence for each render is: 1. Component body runs; `usePrevious` reads `ref.current`, which still holds the value written after the *last* commit. 2. React commits the output. 3. The effect fires and sets `ref.current = value`, arming the ref for the next render. Note the deliberate absence of a dependency array. The effect must run after *every* commit, otherwise the stored value drifts out of sync whenever the component re-renders for an unrelated reason and `value` later changes. ## What "previous" actually means here A common misunderstanding is that the hook gives you the previous *value* — the value before the last change. It does not: it gives you the value at the previous *render*. If the component re-renders three times while `value` stays `5`, the hook returns `5`, not whatever came before the 5. That is usually what you want for comparisons like "did this prop change since last time", but it is not a change history. The first render is the other edge: the effect has never run, so `ref.current` is the initial `undefined`. In TypeScript, type it honestly: ```ts function usePrevious<T>(value: T): T | undefined { const ref = useRef<T | undefined>(undefined); useEffect(() => { ref.current = value; }); return ref.current; } ``` Seeding the ref with the initial value instead (`useRef(value)`) makes the first render report "previous === current", which hides the mount case rather than solving it; callers then cannot distinguish "unchanged" from "first time". ## When to reach for it, and when not to `usePrevious` is genuinely useful for comparing a rendered value to its last rendered version: animating a number in the direction it moved, logging transitions, or firing an imperative call only when a prop actually changed. It is a poor substitute for two things people misuse it for. First, deriving state — if you need a value computed from props, compute it during render rather than tracking the old one. Second, resetting state when an identity changes: giving the component a `key` tied to that identity re-mounts it with fresh state and is both simpler and less error-prone than diffing. ## Variants worth knowing If you want the previous value only when it changed, compare inside the hook and store both: ```js function usePreviousDistinct(value) { const current = useRef(value); const previous = useRef(undefined); if (!Object.is(current.current, value)) { previous.current = current.current; current.current = value; } return previous.current; } ``` This version mutates during render, which is the trade-off it makes to be correct immediately rather than one commit later — and it is only defensible because the mutation is idempotent for a given `value`, so a replayed render produces the same answer. It is worth being able to explain that trade-off rather than presenting either version as the one true implementation.

  • Why not just write `ref.current = value` at the end of the component body and skip the effect?
    Because the body then returns the value it just wrote — the hook would report the current value, not the previous one — and because mutating during render breaks purity. React may abandon and replay a render under concurrent rendering, and StrictMode double-invokes bodies in development, so an in-body write can execute twice per commit and corrupt the stored value.
  • What happens if you give the effect a dependency array of `[value]`?
    For this hook it happens to behave the same, because the ref only needs updating when `value` changes; a re-render with an unchanged `value` leaves the ref holding that same value. It is a defensible micro-optimisation, but omitting the array is the safer default: the invariant "the ref matches the last committed render" then holds unconditionally.
  • A colleague uses usePrevious to reset internal state when a `userId` prop changes. Is that a good use?
    No — that is the case for `key`. Rendering the component as `<Profile key={userId} … />` makes React unmount the old instance and mount a fresh one with initial state, which is one line and cannot drift. Diffing the previous id in an effect resets a render late, misses fields you forget to clear, and grows a branch for every piece of state.

saying these in an interview costs you the question

  • Assigning ref.current during the component body and calling it correct
  • Using useState to hold the previous value
  • Claiming it returns the value before the last change, not the last render
  • Insisting the hook must never return undefined
  • Adding a dependency array 'because effects always need one'

context