Easy

usePrevious

Prompt

Create a custom hook usePrevious that accepts any value and returns what that value was on the previous render. On the first render, it should return undefined.

Playground

Hint 1

You need something that persists between renders but doesn't trigger a re-render when updated. That's useRef. Create a ref to store the "old" value.

Hint 2

useEffect runs after the render. So if you return ref.current first (during the render) and then update ref.current = value inside useEffect (after the render), the returned value is always one render behind. That's exactly what "previous" means.

Solution

Explanation

This hook looks deceptively simple, but there's a clever timing trick at its core that's worth understanding deeply. The key insight is that useEffect runs after the render is painted to the screen, but return ref.current runs during the render. That one-step delay is what gives us the "previous" value.

export function usePrevious(value) {
const ref = useRef();

useEffect(() => {
ref.current = value;
});

return ref.current;
}

Let's trace through a real scenario where count starts at 0 and the user clicks increment:

Render 1 (count = 0): The ref was just created, so ref.current is undefined. We return undefined (there's no "previous" yet). After the render paints, the effect runs and saves ref.current = 0.

Render 2 (count = 1): During this render, ref.current is still 0 (the value saved after the last render). We return 0. That's our previous value. After this render paints, the effect runs and updates ref.current = 1.

Render 3 (count = 2): ref.current is 1. We return 1. The effect updates it to 2. And so on.

See the pattern? The returned value is always one render behind. That's the entire hook.

Why useRef and not useState?

This is a great question to ask yourself. If we used useState to store the previous value, updating it inside useEffect would trigger another re-render, which would update it again, and we'd be stuck in an infinite loop. useRef is perfect here because changing ref.current doesn't trigger a re-render. It's just a mutable box that persists between renders.

Why no dependency array?

You might notice we don't pass a dependency array to useEffect. That means the effect runs after every single render. We want this because the component might re-render for reasons unrelated to value (maybe a parent re-rendered), and we still want the ref to stay up to date. Omitting the dependency array keeps the behavior consistent: the ref always reflects the value from the most recent render, no matter what caused the re-render.