Topics in this subject
React 3 min read Updated 5 Aug 2026

useRef & the DOM

useRef for DOM access and mutable render-persistent values that don't trigger re-renders; forwardRef and pitfalls.

Two jobs of useRef 🎯#

useRef(initial) returns a stable { current } object that persists across renders. Two uses:

  1. Access a DOM node — attach the ref to a JSX element's ref prop.
  2. Hold a mutable value across renders — like an instance variable, without triggering a re-render when it changes.
const ref = useRef(null);        // stable object, same identity every render
ref.current = anything;          // mutating does NOT re-render

Accessing DOM nodes#

React sets ref.current to the DOM node after commit, and back to null on unmount.

function SearchBox() {
  const inputRef = useRef(null);
  useEffect(() => { inputRef.current.focus(); }, []);   // focus on mount

  return <input ref={inputRef} placeholder="Search…" />;
}

Common DOM uses: focus, scroll into view, measure size/position, and integrating imperative libraries.

inputRef.current.focus();
boxRef.current.scrollIntoView({ behavior: "smooth" });
const { width, height } = boxRef.current.getBoundingClientRect();  // measure

Mutable value that survives renders#

Unlike state, updating a ref doesn't re-render — ideal for values you read/write imperatively but that shouldn't drive the UI (timer IDs, previous values, latest-callback holders).

function Timer() {
  const idRef = useRef(null);              // holds interval id across renders
  const start = () => { idRef.current = setInterval(tick, 1000); };
  const stop  = () => clearInterval(idRef.current);
  // ...
}
useState useRef
Change triggers re-render Yes No
Value persists across renders Yes Yes
Read latest synchronously After re-render Immediately via .current
Use for UI data DOM nodes, non-UI mutable values

forwardRef#

Refs aren't passed through like normal props. To let a parent ref reach a child's DOM node, wrap the child in forwardRef.

const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);

function Parent() {
  const ref = useRef(null);
  return <Input ref={ref} />;   // ref now points to the inner <input>
}

⚠️ In React 19, ref can be received as a normal prop in function components, making forwardRef largely unnecessary (it's being deprecated). Use useImperativeHandle to expose a curated imperative API instead of the raw node.

⚠️ Don't read/write refs during render#

Rendering must be pure. Mutating or reading ref.current during render is unpredictable because React may render without committing. Do ref work in effects or event handlers, where the DOM is committed.

function Bad() {
  const ref = useRef(0);
  ref.current++;               // ⚠️ side effect during render
  return <div>{ref.current}</div>;
}
flowchart LR
  R["Render (pure)"] --> C["Commit to DOM"]
  C --> P["Paint"]
  P --> E["Effect / event handler"]
  E -->|"read & write ref.current here"| OK["Safe"]
  R -.->|"mutate ref here"| BAD["Unsafe ⚠️"]

Interview Q&A#

Q1. What are the two main uses of useRef? Accessing a DOM node via the ref prop, and storing a mutable value that persists across renders without causing a re-render.

Q2. How does useRef differ from useState? Both persist across renders, but changing a ref doesn't re-render and is applied synchronously via .current; changing state schedules a re-render.

Q3. Why doesn't mutating ref.current update the UI? React doesn't track ref mutations. The { current } object is stable and changes to it bypass the render cycle entirely — that's the point.

Q4. What is forwardRef for? It lets a parent's ref pass through a component to reach a child's DOM node, since ref isn't a normal prop. In React 19 ref is a regular prop, so forwardRef is being deprecated.

Q5. Why avoid reading/writing refs during render? Render must be pure and can run without committing; touching ref.current there yields inconsistent results. Do it in effects or event handlers after commit.