React Curriculum

useRef: DOM Access & Mutable Containers

Medium100% Free~25 mins#react#useref#dom#mutable-container#refs#commit-phase
Interview Answer (30-Second Summary)

useRef returns a plain JavaScript object with a persistent { current: value } property that survives across renders without triggering a re-render when mutated. It is used for directly referencing DOM nodes and storing mutable values (timers, previous state) that do not affect the visual UI.

Key Learning Objectives
✓

Differentiate between useRef (persistent mutable container) and useState (render-triggering snapshot).

✓

Explain how React sets ref.current to DOM elements during the commit phase and clears them to null on unmount.

✓

Use useRef to store interval IDs, websocket instances, and previous prop snapshots.

✓

Understand why reading or writing ref.current during rendering is an anti-pattern in Concurrent React.

The Interview Problem & Context

What is logged to the console when mutating ref.current across renders compared to updating component state?

1// Simulating useRef vs useState mechanics
2function simulateRefBehavior() {
3 let renderCount = 0;
4 const countState = 0;
5 const countRef = { current: 0 };
6
7 // Mutating ref does NOT trigger a re-render
8 countRef.current = 5;
9 const rendersAfterRefMutation = renderCount; // Still 0
10
11 // State update schedules re-render (renderCount becomes 1)
12 const isSameRefObjectAcrossRenders = true;
13
14 return `${rendersAfterRefMutation} ${countRef.current} ${countState} ${isSameRefObjectAcrossRenders}`;
15}
16
17console.log(simulateRefBehavior());
Predict Console & Render Output
A

0 5 0 true

B

1 5 5 true

C

0 0 0 false

D

1 5 0 false

Execution Trace
Step 1 of 3 (Line 4)

Initializes state and persistent ref object container.

Component / Call Stack
simulateRefBehavior()
Hook State & Scope
renderCount:0
countRef.current:0
Console Output(No console logs emitted)
Deep Architectural Explanation

The Two Core Use Cases of useRef

1. Referencing DOM Elements

When you pass a ref to a JSX element (<input ref={inputRef} />):

  • During the Render Phase, inputRef.current is still null.
  • During the Commit Phase, after the DOM node is created, React sets inputRef.current = HTMLInputElement.
  • When the element unmounts, React sets inputRef.current = null.
    This allows imperative actions: inputRef.current.focus(), scrollIntoView(), or measuring element bounding boxes.

2. Persistent Mutable Container

useRef is essentially equivalent to:

ts
// Conceptual implementation of useRef
function useRef(initialValue) {
  const [ref] = useState({ current: initialValue });
  return ref;
}

It gives you an "instance variable" that survives across all renders. It is ideal for:

  • Storing timer IDs (setInterval)
  • Storing previous props or state values
  • Storing WebSocket or audio stream instances

The Purity Rule: Never Read or Write Refs During Render!

In Concurrent React:

tsx
function Bad() {
  const countRef = useRef(0);
  countRef.current++; // ❌ ANTI-PATTERN: Mutating ref during render!
  return <h1>{countRef.current}</h1>; // ❌ Reading ref during render!
}

Because render functions may be called multiple times or aborted, mutating refs during render leads to unpredictable, non-deterministic values. Only read and write refs inside effects or event handlers.

Common Interview Traps & Pitfalls
✕

Reading or writing ref.current during rendering (inside the component body or JSX expression).

✕

Using state to hold timer IDs or animation frame references, causing unnecessary re-renders whenever a timer is set.

✕

Expecting ref updates to immediately trigger a UI re-render.

Senior Interviewer Follow-Up Probes
?

How does React 19 change the way refs are passed to custom components compared to forwardRef in React 18?

?

How do you handle multiple refs dynamically in a loop using callback refs?

?

Why does React set DOM refs during the commit phase rather than the render phase?

Frequently Asked Interview Questions
What is the difference between useRef and useState?

useState stores data that drives the UI, and updating it triggers a component re-render. useRef stores a mutable value that persists across renders without triggering a re-render when mutated.

Component rendering must be a pure function. In Concurrent React, renders can be paused, restarted, or discarded; mutating refs during render breaks idempotency and causes unpredictable UI.

Instead of passing a ref object, you pass a function: ref={node => { ... }}. React calls this function with the DOM node on mount and null on unmount, which is ideal for dynamic lists of refs.

During the commit phase, after React has created the DOM nodes and before running useLayoutEffect or useEffect.

Yes. By updating ref.current = value inside a useEffect, ref.current will hold the previous render's value during the next render execution.