AniUI Academy

useRef for the DOM and Mutable Values

The one hook that holds a mutable value across renders without causing a re-render when it changes — for reaching a real DOM node, and remembering things the screen shouldn't reflect.

9 min read

Every hook so far either produces something to render (props, state) or runs in sync with rendering (effects). useRef is different: it hands you a mutable box that survives across renders, without ever asking React to re-render when its contents change.

The shape

import { useRef } from "react";
 
const myRef = useRef(initialValue);
// myRef is { current: initialValue }, and stays the SAME object across renders

useRef returns a plain object with a single property, current, set to whatever initial value you passed. That object is created once and reused for the component's entire lifetime — mutating myRef.current doesn't trigger a re-render, and reading it later (in a different render) sees whatever it was last set to.

Reaching an actual DOM node

The most common use: getting a handle on a real DOM element to call an imperative browser API React doesn't expose declaratively, like focusing an input.

import { useRef } from "react";
 
function SearchBox() {
  const inputRef = useRef(null);
 
  function focusInput() {
    inputRef.current.focus();
  }
 
  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus the input</button>
    </>
  );
}

Passing a ref to an element's ref attribute is special-cased by React: once that element mounts, React sets inputRef.current to the actual DOM node. Before the first render, or if the element is conditionally not rendered, .current is null — which is why useRef(null) (not some other default) is the conventional starting value.

Mutable values that shouldn't cause a re-render

The second common use has nothing to do with the DOM: storing a value the component needs to remember, where changing it should not re-render anything — a timer id, a flag, the previous value of a prop.

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef(null);
 
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setSeconds((s) => s + 1);
    }, 1000);
 
    return () => clearInterval(intervalRef.current);
  }, []);
 
  return <p>{seconds}s elapsed</p>;
}

Here, intervalRef.current holds the interval id purely so the cleanup function can clear it later. If this were stored in state instead, setting it would trigger a pointless extra re-render every time the component starts a new interval — a re-render that has nothing to do with anything actually displayed.

Refs vs. state, side by side

useStateuseRef
Changing itTriggers a re-renderDoes not trigger a re-render
Value shown in UITypically yesTypically no — internal bookkeeping
Read during renderSafe and expectedAvoid — see below

Don't read (or write) a ref during render

Because ref updates bypass React's rendering entirely, using a ref's value to decide what to render breaks the assumption that render is a pure, predictable function of props and state — the same assumption the reconciliation lesson relied on. Refs are meant to be read and written inside effects and event handlers — after render, in response to something that already happened — not consulted while deciding what the render itself should produce.

// Don't do this — reading a ref during render
function Weird() {
  const countRef = useRef(0);
  countRef.current++; // mutating during render — avoid
  return <p>{countRef.current}</p>; // won't reliably update the screen anyway
}

If a value needs to affect what's displayed, it needs to be state, not a ref — that's the entire reason the two hooks exist separately.

Try it yourself

Try it yourself
Loading playground...

What to remember

  • A ref is a mutable box ({ current: ... }) that persists across renders without ever triggering a re-render when it changes.
  • Pass a ref to an element's ref attribute to get a handle on the real DOM node once it mounts.
  • Use a ref for bookkeeping the component needs internally — timer ids, flags, previous values — that shouldn't affect what's rendered.
  • Don't read or write a ref during render itself; refs belong in effects and event handlers, and anything that should affect the UI belongs in state instead.

Check yourself

4 questions · pass 3/4 to unlock Composition and Children

up to 50
  1. 1.What is the key difference between state (useState) and a ref (useRef)?

  2. 2.How do you get a reference to an actual DOM node, like to call .focus() on an input?

  3. 3.Why would you use a ref instead of state to store something like a timer id or the previous value of a prop?

  4. 4.What does reading myRef.current during render (not inside an effect or event handler) risk?

4 left to answer