What’s the difference between useRef and useState? When should each be used?

MediumIntermediateReact
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

Explain how useState triggers re-renders and is used for UI state, while useRef holds a mutable value that persists across renders without re-rendering — and when each is the correct tool (DOM refs, timers, previous values, avoiding stale closures, and render-driving state).

Answer

Short answer

useState is for data that should drive what you render (changing it re-renders). useRef is for data you want to persist across renders but changing it should not trigger a re-render (a mutable container).

Hook

What it stores

What happens when it changes

useState

A render-driving value (UI state)

✅ Triggers a re-render

useRef

A mutable container: { current: ... }

❌ Does NOT trigger a re-render

The core difference

First-principles mental model

React re-renders when you want the UI to reflect new data. So ask yourself one question:

"Should changing this value update what the user sees right now?"

If yes → useState.
If no (but you still need the value to persist) → useRef.

JSX
function Example() {
  const [count, setCount] = useState(0);  // UI should update when this changes
  const clicksRef = useRef(0);           // internal bookkeeping; UI doesn't need it

  const onClick = () => {
    clicksRef.current += 1;              // won't re-render
    setCount((c) => c + 1);              // will re-render
  };

  return (
    <button onClick={onClick}>
      Count: {count}
    </button>
  );
}
                  

Common useState use cases

Use useState when the value affects rendering: text, visibility, selection, form inputs, loading state, errors, filters, toggles, etc.

Scenario

Correct tool

Why

Show/hide a modal

useState

UI must update immediately

Form input value

useState

Render depends on current value

Loading / error / fetched data

useState

UI should reflect async results

Selected tab / filter

useState

Directly affects what is shown

When useState is the right choice

Common useRef use cases

Use useRef when you need a value to persist, but you don’t want a render every time it changes.

Scenario

Why useRef fits

Example

DOM element access

Imperative access to a node

focus(), scrollIntoView()

Timer / interval IDs

Store mutable IDs across renders

setTimeout / setInterval cleanup

Latest value for async callbacks

Avoid stale closures without rerendering

read latest state in an interval

Previous value tracking

Persist last render's value

prevCount.current

When useRef is the right choice
JSX
function Search() {
  const inputRef = useRef(null);

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current?.focus()}>
        Focus input
      </button>
    </div>
  );
}
                  

The big trap: using useRef as "state"

People do this to avoid re-renders, but it’s usually a bug factory. If the UI depends on it and you store it in a ref, the UI won’t update and you’ll get out-of-sync screens.

JSX
function Bad() {
  const valueRef = useRef('');

  return (
    <div>
      <input onChange={(e) => (valueRef.current = e.target.value)} />
      <p>Typed: {valueRef.current}</p> {/* ❌ UI won't update reliably */}
    </div>
  );
}

function Good() {
  const [value, setValue] = useState('');

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <p>Typed: {value}</p> {/* ✅ UI updates */}
    </div>
  );
}
                  

Rule of thumb that wins interviews

useState = "React should re-render when this changes."
useRef = "I need to remember something between renders, but it’s not part of the UI."

Bonus: combining both (best of both worlds)

Sometimes you want rendering plus a latest value for async callbacks (avoiding stale closures). Keep the UI in state and mirror it into a ref:

JSX
function Example() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  useEffect(() => {
    countRef.current = count;
  }, [count]);

  useEffect(() => {
    const id = setInterval(() => {
      console.log('latest:', countRef.current);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
}
                  
Summary

useState holds render-driving state and triggers re-renders. useRef holds a mutable value that persists across renders but does not trigger re-renders. Use state for anything that affects UI, and refs for DOM access, timers, latest values in async callbacks, and other non-visual bookkeeping.

Similar questions
Guides
18 / 41