useRef in React

Hero image for UseRef in React. Image by Patrick Schneider.
Hero image for 'UseRef in React.' Image by Patrick Schneider.

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 rerender, 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 rerender, 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 rerender 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 oneoff 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 nonReact APIs, refs often become the bridge:

  • DOM methods
  • thirdparty widgets
  • timers
  • subscriptions

That is because many of those APIs are imperative. They want a stable handle to an element or an instancelike value. Refs provide that handle.


useRef is not a loophole around state

This is where misuse begins.

Because refs can store mutable values without rerendering, 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 longlived 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.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript
  5. React