Why useRef Feels Simple Until It Doesnât
useRef is often introduced as a way to access DOM elements, but its real purpose goes beyond that. It provides a persistent container that can store values across renders without triggering re-renders.
At first, useRef looks almost too simple to care about.
Most explanations stop at:
âItâs used to access DOM elementsâ
And for a while, that feels enough.
You use it to focus an input, maybe scroll to something, and move on.
But then you start seeing code that doesnât involve the DOM at all.
const valueRef = useRef(0)No element. Just a number.
Thatâs usually the point where things stop making sense.
The Problem With the Usual Explanation
Saying that useRef is for DOM access is not wrong.
Itâs just incomplete.
Because it doesnât explain why you can store anything inside it, or why changing it doesnât trigger a re-render.
To understand that, you need to shift the way you look at it.
What useRef Actually Gives You
At its core, useRef returns a persistent object.
const ref = useRef(initialValue)Which behaves like:
{ current: initialValue }That object stays the same across renders.
Not recreated. Not reset. The same reference every time.
And React doesnât track changes inside it.
You can update current freely, and nothing will happen from Reactâs side.
Why That Matters
Reactâs normal behavior is predictable:
- state changes â component re-renders
- props change â component re-renders
Refs donât follow that rule.
They sit outside of Reactâs rendering system.
Thatâs exactly what makes them useful.
When useRef Starts to Click
The real use case appears when you need to store something, but re-rendering doesnât make sense.
A good example is working with external libraries.
In my case, it was Chart.js.
There was an âactive indexâ when clicking on the chart.
Using state felt natural at first, but something didnât feel right.
React wasnât actually rendering the chartâChart.js was.
So triggering a React re-render just to update an internal chart state felt unnecessary.
Using Ref Instead of State
const activeIndexRef = useRef<number | null>(null)
onClick: (_, elements, chart) => {
if (!elements.length) return
const index = elements[0].index
if (activeIndexRef.current === index) {
activeIndexRef.current = null
} else {
activeIndexRef.current = index
}
chart.update()
}Here, the value persists between clicks, but React stays out of it.
No extra render cycle.
Just direct control over the chart.
A More Useful Way to Think About It
Instead of tying useRef to the DOM, itâs more helpful to think in terms of behavior.
useStateâ triggers UI updatesuseRefâ stores values silently
Or even simpler:
A ref is a place to store something that React doesnât need to react to
That âsomethingâ can be:
- a DOM element
- a number
- a previous value
- an external instance
Final Takeaway
The confusion around useRef usually comes from learning it through a narrow use case.
Once you stop thinking of it as âthe DOM hookâ and start seeing it as a persistent container, it becomes much easier to use correctly.
Itâs not about what you store in it.
Itâs about whether React needs to care when it changes.
If the answer is no, useRef is probably the right tool.