Skip to content
·5 min read

When to Use useRef vs useState (The Answer Is Almost Always State)

Refs hold values without re-rendering, which makes them tempting for exactly the wrong jobs. The line between ref and state is simpler than most explanations make it.

ReactHooksWeb Dev

Every React codebase has one mysterious bug where the screen shows old data, nothing errors, and the fix turns out to be a ref holding something the UI was supposed to display. The developer reached for useRef because "it avoids re-renders", which is the ref's most quoted feature and its most common misuse.

The line between ref and state is one question.

Who needs to see the change?

If the UI should update when the value changes, it is state. If nothing on screen depends on the value, it is a ref. That is the entire decision, and it resolves almost every case in a codebase:

  • The value sits in a heading, a badge, a form field: state
  • A timer id, a previous value, a has-mounted flag: ref
  • A DOM node you need to focus or measure: ref
  • Anything a user would notice if it froze forever: state

State changes schedule renders. Ref changes mutate a box. When the UI depends on the box, it renders the box's value from last render, forever, with no error to debug.

When unsure, pick state

State in the wrong place costs a re-render. A ref in the wrong place costs a stale screen and an afternoon. The asymmetry makes the default obvious: reach for state, and downgrade to a ref only when you can name what re-render you are preventing.

Performance is the wrong reason anyway. If renders are actually slow, memoization, smaller components, or moving state closer to where it is used beat hiding data from React every time.

The legitimate list is short and boring

Refs earn their place in four jobs: DOM access (focus, scroll, measurement, playback), storing interval and timeout ids, keeping the previous value of something, and holding the latest callback so subscriptions stay stable. All bookkeeping. None of it drawn.

Notice what the list has in common: every entry is a value React does not need to react to. That is the test. If React does not need to know, a ref. If it does, state.

The rule that makes the others work

Never read or write a ref during render. Refs belong in event handlers and effects. Reading during render makes the output depend on mutable data React cannot see, and writing during render breaks the moment Strict Mode double-invokes. Render stays pure, refs change outside it, and the whole system stays predictable.

If your bug of the month is a screen showing stale data, audit the refs first. Somewhere in there is a value that should have been state.