All posts
ReactTypeScriptWeb DevelopmentFrontend DevelopmentProgramming

useState vs useRef: a tricky React interview question

June 25, 2026 · 3 min read


useState vs useRef: a tricky React interview question

The task looks trivial: here’s a counter — now also show its previous value.

function App() {
    const [counter, setCounter] = useState(0);
    
    return (
        <>
            <button onClick={() => setCounter(c => c - 1)}>-1</button>
            <button onClick={() => setCounter(c => c + 1)}>+1</button>
            <span>counter: {counter}</span>
            {/* show the previous counter value here */}
        </>
    );
}

My first instinct was a second piece of state — store the old value before updating. It works, but it's the wrong tool: you have to remember to set it in every handler, and you trigger an extra re-render just to remember a value React was about to discard.

Attempt 1: just add more state

function App() {
    const [counter, setCounter] = useState(0);
    const [prevCounter, setPrevCounter] = useState(null);

    function increment() {
        setPrevCounter(counter);
        setCounter(counter + 1);
    }
        
    function decrement() {
        setPrevCounter(counter);
        setCounter(counter - 1);
    }

    return (
        <>
            <button onClick={decrement}>-1</button>
            <button onClick={increment}>+1</button>
            <span>counter: {counter}</span>
            <span>previous: {prevCounter}</span>
        </>
    );
}

It works. So what’s wrong with it? Two things. You have to remember to set prevCounter in every handler that touches counter — miss one and prevCounter silently goes stale. And you trigger an extra state update and re-render just to remember a value React was about to throw away.

Ask yourself the question that actually unlocks this: do I need this value to drive the UI on its own, or do I just need to remember it between renders? That distinction is the whole interview.

The insight: state and refs run on different schedules

Updating state schedules a re-render. Updating a ref does not — a ref is just a box that survives across renders and that you can mutate without telling React. “Previous value” isn’t UI state; it’s a memory of what the last render showed. That’s a ref’s entire job.

Attempt 2: useRef, written in the effect body

function App() {
    const [counter, setCounter] = useState(0);
    const prevCounter = useRef(null);

    useEffect(() => {
        prevCounter.current = counter; // runs after the render
    }, [counter]);

    // render shows prevRef.current (old) → then the effect updates it
    return (
        <>
            <button onClick={() => setCounter(c => c - 1)}>-1</button>
            <button onClick={() => setCounter(c => c + 1)}>+1</button>
            <span>counter: {counter}</span>
            <span>previous: {prevCounter.current}</span>
        </>
    );
}

The one fact that makes this work: render always runs before the effect. When counter goes 0 → 1, the component renders with counter = 1 while prevCounter.current still holds 0 — so the screen shows "counter: 1, previous: 0" Then the effect fires and updates the ref to 1, ready to be "previous" when counter becomes 2.

The question looked like it was about a counter. It was really about knowing when something belongs in state versus a ref.

What’s a React question that looked trivial until you actually tried to answer it?