
useRef in React

useRef is one of those React hooks that looks modest at first and then turns out to be quietly useful all over the place.
Many developers first meet it as "the hook for grabbing a DOM node". That is true, but incomplete. A ref can also hold mutable values across renders without triggering a re‑render, which gives it a second, equally practical role.
Those two roles are easier to use well once we keep them separate in our heads:
- DOM access
- persistent mutable storage
The basic shape of useRef
At its simplest:
const inputRef = useRef<HTMLInputElement | null>(null);React returns an object with a current property. That object stays the same between renders.
If we attach the ref to an element:
<input ref={inputRef} />then inputRef.current points at the underlying DOM node after render.
Focusing an Input is the Classic Example
This is often the first genuinely useful ref example because it shows why state alone is not enough.
const SearchBox = (): JSX.Element => { const inputRef = useRef<HTMLInputElement | null>(null); const focusInput = (): void => { inputRef.current?.focus(); }; return ( <> <input ref={inputRef} /> <button onClick={focusInput}>Focus search</button> </> );};We are not storing display data here. We are reaching into the DOM for an imperative action.
That is one of the legitimate places where refs shine.
Why This is Not State
If a value should affect what the component renders, it usually belongs in state.
If a value just needs to persist between renders without causing a re‑render, a ref may be a better fit.
That distinction matters because updating a ref does not trigger a render:
const countRef = useRef(0);countRef.current += 1;React will not re‑render the component because of that change.
That is not a bug. It is the design.
Refs are Good for Values That React Does Not Need to display
For example:
- timer IDs
- previous values
- scroll positions
- mutable integration handles
- whether a one‑off effect has already run
These are values the component may need to remember, but not values that should directly drive rendered output.
Tracking a Previous Value is a Practical Example
Suppose we want to compare the current prop with the previous one:
const PriceChange = ({ price }: { price: number }): JSX.Element => { const previousPriceRef = useRef<number | null>(null); useEffect(() => { previousPriceRef.current = price; }, [price]); return ( <p> Previous price: {previousPriceRef.current ?? 'none'} </p> );};The ref holds onto the earlier value between renders, but updating it does not create an extra render cycle by itself.
This Makes Refs Useful for Integration Work
When React components touch non‑React APIs, refs often become the bridge:
- DOM methods
- third‑party widgets
- timers
- subscriptions
That is because many of those APIs are imperative. They want a stable handle to an element or an instance‑like value. Refs provide that handle.
useRef is not a loophole around state
This is where misuse begins.
Because refs can store mutable values without re‑rendering, developers sometimes use them to avoid state updates altogether. That usually leads to confusing components because the rendered UI no longer clearly follows the underlying data.
If the interface must update when a value changes, use state.
If the value is internal bookkeeping or imperative plumbing, a ref may be appropriate.
That Means Refs and State Answer Different Questions
State answers:
"What data should React render from right now?"
Refs answer:
"What value or handle should this component keep around without making rendering depend on it?"
Once you frame the two tools that way, the choice becomes much easier.
Refs Also Help Avoid Stale External Handles
Suppose we store a timeout ID:
const timeoutRef = useRef<number | null>(null);const startDelay = (): void => { timeoutRef.current = window.setTimeout(() => { console.log('Done'); }, 1000);};const cancelDelay = (): void => { if (timeoutRef.current !== null) { window.clearTimeout(timeoutRef.current); }};This value needs to persist between renders, but there is no reason for updating it to cause the component to redraw. That is exactly the kind of case refs handle well.
DOM Access Should Still Be Deliberate
React is mostly declarative, so reaching into the DOM should be purposeful rather than constant.
Using a ref to manage focus, text selection, or scroll position is sensible.
Using refs as a default substitute for React's ordinary data flow usually is not.
The point is not to avoid imperative code entirely. The point is to keep it contained to the places that genuinely need it.
Refs Survive Renders Because the Object Identity is Stable
This is the mechanism underneath the feature's usefulness.
The component may render again and again, but the ref object itself is preserved, which is why current can keep changing while the component remains mounted.
That stable identity is what makes refs suitable for long‑lived handles.
The Best Test is Whether the UI Depends on the Value
If the answer is yes, prefer state.
If the answer is no, and the value simply needs to persist for internal logic or imperative interaction, a ref is often right.
That simple test prevents a lot of confused React code.
useRef is small, but extremely practical
Not every React hook changes architecture. Some just solve everyday problems cleanly.
useRef is one of those. It gives us a reliable way to keep hold of DOM nodes and persistent mutable values without pretending that everything belongs in component state.
Once you understand that boundary, the hook becomes much easier to trust and much harder to misuse.
Related Articles

Exploring the Liquid Templating Language. 
LeetCode: Removing the nth Node from the End of a List. LeetCode: Removing the
nthNode from the End of a List
Breadth‑First Search: Solving Binary Tree Level Order Traversal. Breadth‑First Search: Solving Binary Tree Level Order Traversal

React's Reconciliation Algorithm Explained. React's Reconciliation Algorithm Explained

Invoked Function Expressions (IIFE). Invoked Function Expressions (IIFE)

Using JavaScript to Avoid Orphans. Using JavaScript to Avoid Orphans

JSON.parse() and JSON.stringify() Explained for Beginners. JSON.parse()andJSON.stringify()Explained for Beginners
Is a Software Engineer High Paying? Is a Software Engineer High Paying?

Flattening Arrays in JavaScript. Flattening Arrays in JavaScript

Interpolation: Sass Variables Inside calc(). Interpolation: Sass Variables Inside
calc()
Monotonic Stack: Solving the 'Daily Temperatures' Problem. Monotonic Stack: Solving the 'Daily Temperatures' Problem

Replace Inline Styles in Gatsby with an External CSS File. Replace Inline Styles in Gatsby with an External CSS File