React FundamentalsPart 7

useRef — Remembering a Value Without Touching the Screen

A look at useRef, which unlike State doesn't trigger a re-render when it changes. How it differs from a plain variable and a module-level let, why you shouldn't read or write it during render, and its most common use — reaching into the DOM.

The State we’ve covered so far was a value that “redraws the screen when it changes.” But you can have the exact opposite need: a value you want to remember across renders, yet whose change should not redraw the screen. That’s what useRef is for.

For example, values like “the previous scroll position,” “a counter for how many times it rendered,” or “a timer’s ID” need to be remembered but don’t appear on screen directly. Put these in State and you’d needlessly redraw the screen every time they change.


useRef — a memory box with no effect on rendering

import { useRef } from 'react';

const ref = useRef(initialValue);

useRef returns an object shaped like { current: initialValue }. You read and write the value through ref.current, and this box has two properties:

  • changing it does not trigger a re-render (the decisive difference from State)
  • its value persists across re-renders (the difference from a plain local variable)

Set side by side with State, the character is clear.

Change → re-render?Persists across re-renders?Use
State✅ yesvalues that must reflect on screen
useRef❌ novalues to remember, unrelated to the screen

Why not just a variable or a module-level let?

“If it’s a remembered value that doesn’t touch the screen, can’t I just put a let outside the component?” you’d think. And indeed you can make a ‘persisting value that doesn’t re-render’ that way. But there’s a decisive problem.

A let outside the module is shared by all instances of that component. Show the same component three times on screen and the three share one variable, overwriting each other’s value. useRef, by contrast, gives each component instance its own independent box. Three on screen means three boxes, with no interference.

A local variable inside the component has the opposite problem. It’s independent per instance, but as we saw it runs again on every render and resets each time. useRef is the one place that satisfies both properties at once: “independent per instance, yet persisting across renders.”


Don’t read or write it during render

useRef has one important rule: don’t read or write ref.current during rendering.

function MyComponent() {
  myRef.current = 123;              // 🚩 no writing during render
  return <h1>{myRef.current}</h1>;  // 🚩 no reading during render
}

In Part 5 we said rendering must be pure. But a ref is a value that doesn’t notify React when it changes, so reading or writing it during render makes the screen depend on “a value you can’t tell when changed” — unpredictable. Instead, refs should be handled inside event handlers or effects.

function MyComponent() {
  useEffect(() => {
    myRef.current = 123;           // ✅ fine inside an effect
  });
  function handleClick() {
    doSomething(myRef.current);    // ✅ fine inside an event handler
  }
}

Touching it once, after rendering is done, when some event occurs — that’s the right rhythm for a ref.


The most common use — reaching into a DOM element

Actually, the place you’ll meet useRef most in practice is elsewhere: when you need to point directly at a specific DOM element. Automatically focusing an input, measuring an element’s size, moving the scroll — things like that.

function SearchBox() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // focus the input on mount
  }, []);

  return <input ref={inputRef} />;
}

Attach ref={inputRef} to a JSX element and, after render, React puts that actual DOM node into inputRef.current. Even while you draw the screen declaratively, useRef is the official escape hatch for those moments when you must handle an element imperatively.


State or Ref?

One last rule of thumb. If the value must be visible on screen, use State; if it just needs to be remembered, unrelated to the screen, use Ref. A common mistake is to confuse the two — putting “a value that should show on screen” in a ref so the screen never updates, or putting “a value unrelated to the screen” in state so it triggers needless re-renders.

We’ve now come through the values a single component handles on its own (State and Ref). Next time we’ll widen the view to how distant components share a value — going one step beyond Props, into Context.