I reach for useState when a value needs to drive a re-render: form inputs, toggle flags, fetched data. I reach for useRef when I need to persist a value across renders without triggering one: storing a timer ID, capturing a previous prop for comparison, or holding a DOM node. The critical distinction is that mutating ref.current is invisible to React, so it never schedules a paint. The mistake I see most is using a ref to hold something the UI depends on, then wondering why the screen does not update. My rule: if the display changes when the value changes, that is state. If it is bookkeeping the component needs but the user never sees, that is a ref.
Insider read
Really testing: Whether you understand the render cycle contract and can articulate the full class of non-rendering persistent values useRef covers, not just DOM node references.
The tell: Juniors say useRef is for accessing DOM elements. Seniors explain the render cycle contract and name the broader class of non-rendering persistent values: timer IDs, previous prop snapshots, mutable callback holders.
Follow-up: "If you store a callback in a ref to avoid stale closures, what are the tradeoffs?"
Say this"Mutation through ref.current skips React entirely. It works, but you give up React's control over when side effects run. It is a valid escape hatch, not a default pattern."