useRef — DOM Access and Values That Do Not Render
IntermediateA ref is a box whose contents survive re-renders and whose changes never cause one. Two uses: reaching a DOM node, and remembering something the UI does not display.
Overview
useRef covers the cases state cannot. Changing a ref does not re-render, which is exactly wrong for anything the user sees and exactly right for a timer id, a previous value, or a flag that only other code reads. The second use is a DOM handle — focusing an input, measuring an element, controlling a video, handing a node to a non-React library. The decision rule is short: if the UI must update when it changes, it is state; if the UI does not care, it is a ref.
DOM Refs
Attach with the ref attribute, use it in effects and handlers, never during render.
function SearchBox() {
const inputRef = useRef(null)
useEffect(() => {
inputRef.current?.focus() // available after mount
}, [])
return <input ref={inputRef} />
}
// current is null during the first render — the DOM does not exist
// yet — so always use optional chaining.
// Real uses: focus management, scrolling into view, measuring,
// play/pause on media, and passing a node to a library.
node.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
const { height } = node.getBoundingClientRect()
// Forwarding a ref to your own component
const Input = forwardRef(function Input(props, ref) {
return <input ref={ref} {...props} />
})
// In React 19, ref is a normal prop and forwardRef is no longer needed.Refs as Mutable Boxes
Values that persist across renders without triggering any.
// A timer id — nothing on screen depends on it
const timerRef = useRef(null)
function start() {
timerRef.current = setInterval(tick, 1000)
}
function stop() {
clearInterval(timerRef.current)
}
// The previous value of a prop
const prevStatus = useRef(status)
useEffect(() => { prevStatus.current = status }, [status])
// A "has this already happened" flag that must not re-render
const hasTracked = useRef(false)
// The mistake: using a ref for something the UI displays
const count = useRef(0)
count.current++ // screen never updates — no re-render
// If the user should see it, it is state.Choosing Between Them
One question, and the escape hatch warning that goes with it.
// Does the UI need to update when this changes?
// yes -> useState
// no -> useRef
// useState useRef
// re-renders yes no
// read during safe avoid (breaks purity)
// render
// updates asynchronous immediate
// (next render) (right now)
// Do not read or write a ref during render:
function Bad() {
ref.current = ref.current + 1 // impure — breaks with StrictMode,
return <p>{ref.current}</p> // concurrent rendering, and reason
}
// Refs belong in effects and event handlers.
// And the wider point: a ref is an escape hatch out of React's model.
// Reaching for one to manipulate the DOM that React manages — hiding
// an element, setting text — means the state model has a gap.Key Points to Remember
- 1A ref persists across renders and changing it never triggers one
- 2ref.current is null during the first render, so DOM access belongs in effects and handlers
- 3Use a ref for timer ids, previous values and flags — anything the UI does not display
- 4If the user should see the change, it must be state, not a ref
- 5Reading or writing a ref during render breaks purity; a ref is an escape hatch, not a state alternative
Interview Questions
Sign in to ask AriaWhen would you use useRef instead of useState?
Why is ref.current null during the first render?
Why should you not read or write a ref during rendering?
Ask Aria about useRef — DOM Access and Values That Do Not Render
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.