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
Inside a function component, with useRef imported from React, the TypeScript declaration looks like this:
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:
<label>
Search
<input ref={inputRef} />
</label>React assigns the DOM node to inputRef.current when it commits the element, and clears it when the element is removed. Read it in an event handler or effect, after the element has been attached.
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.
import React, { useRef } from 'react';
const SearchBox = (): React.ReactElement => {
const inputRef = useRef<HTMLInputElement | null>(null);
const focusInput = (): void => {
if (inputRef.current !== null) {
inputRef.current.focus();
}
};
return (
<>
<label>
Search
<input ref={inputRef} />
</label>
<button type="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.
Updating a ref from an event handler does not trigger a render. For example, this fragment belongs inside a component, with recordClick attached to its button:
const countRef = useRef(0);
const recordClick = (): void => {
countRef.current += 1;
console.log(countRef.current);
};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
- handles which an effect must release during cleanup
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 a committed price with the previous committed price for an integration or log. We can do that in an effect, then remember the new value:
import React, { useEffect, useRef } from 'react';
const PriceChange = ({ price }: { price: number }): React.ReactElement => {
const previousPriceRef = useRef<number | null>(null);
useEffect(() => {
const previousPrice = previousPriceRef.current;
if (previousPrice !== null && previousPrice !== price) {
console.log('Price changed from', previousPrice, 'to', price);
}
previousPriceRef.current = price;
}, [price]);
return <p>Current price: {price}</p>;
};The effect reads and updates the ref after the render has committed. The paragraph renders from the price prop, not from previousPriceRef.current. If the previous price itself needs to appear in the interface, keep that display value in state.
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
Here is a complete timer example. Starting again cancels the previous timeout, and the effect cleanup cancels any outstanding timeout when the component unmounts:
import React, { useEffect, useRef } from 'react';
const DelayExample = (): React.ReactElement => {
const timeoutRef = useRef<number | null>(null);
const cancelDelay = (): void => {
if (timeoutRef.current !== null) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
const startDelay = (): void => {
cancelDelay();
timeoutRef.current = window.setTimeout(() => {
timeoutRef.current = null;
console.log('Done');
}, 1000);
};
useEffect(() => {
return () => {
if (timeoutRef.current !== null) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
}, []);
return (
<>
<button type="button" onClick={startDelay}>Start delay</button>
<button type="button" onClick={cancelDelay}>Cancel delay</button>
</>
);
};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 whilst 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.