Skip to solution
mediumFrontend

Describe the `useRef` hook and its typical use cases.

1.1k views
01

Understand the problem

Question presented to candidate: "What does useRef give you, and when would you reach for it rather than state?"

What a strong answer should cover:

  • It returns a mutable object with a current property that persists for the component's lifetime.
  • Writing to .current never triggers a re-render. That is the defining property, and it is the feature rather than a limitation.
  • Two distinct use categories: DOM access (attach via the ref attribute) and instance values (timer ids, previous values, mutation flags, mutable caches).
  • The deciding question: does the UI need to update when this changes? Yes means state; no means a ref.
  • Do not read .current during render to decide output — it is not a reactive value, so the render can disagree with reality.
  • useRef(initial) evaluates the initial value on every render even though it is only used once, so avoid expensive expressions there.
  • React 19: ref is a plain prop, and ref callbacks can return a cleanup function.
  • Related: useImperativeHandle for exposing a custom API rather than the raw node; a ref callback for measuring on attach.

Clarifying questions expected:

  • "Does anything rendered depend on this value?" — that single question decides ref versus state.

Code / implementation expected: Yes — a DOM ref and an instance-value ref, ideally showing the render count not moving.

hooksuserefdom manipulationperformance
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — assumes state and effects. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The render counts in section 3 were produced by mutating a ref repeatedly on Re

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

A DOM ref and an instance-value ref, with the render count standing still
Run Playground
import { useRef, useState, useEffect } from "react";

// A tiny helper built entirely on the "persists but does not render" property.
function usePrevious(value) {
  const ref = useRef(undefined);
  useEffect(() => { ref.current = value; }, [value]);
  return ref.current;   // the value from the PREVIOUS render
}

export default function App() {
  const [count, setCount] = useState(0);
  const previousCount = usePrevious(count);

  // 1. A DOM ref — reaching the node imperatively.
  const inputRef = useRef(null);

  // 2. Instance values — remembered, never displayed as they change.
  const clicksNotShown = useRef(0);
  const intervalId = useRef(null);
  const renders = useRef(0);
  renders.current++;

  const [running, setRunning] = useState(false);

  useEffect(() => {
    if (!running) return;
    // The id has to persist so cleanup can clear it — but nothing renders it.
    intervalId.current = setInterval(() => setCount((c) => c + 1), 800);
    return () => clearInterval(intervalId.current);
  }, [running]);

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 500 }}>
      <h4 style={{ marginTop: 0 }}>DOM ref</h4>
      <input ref={inputRef} placeholder="click the button to focus me" style={{ width: 240 }} />{" "}
      <button onClick={() => inputRef.current?.focus()}>focus</button>

      <h4>Instance value — mutating it renders nothing</h4>
      <p style={{ fontSize: 14 }}>
        component renders: <strong>{renders.current}</strong> · ref counter (read
        only when something else renders): <strong>{clicksNotShown.current}</strong>
      </p>
      <p>
        <button onClick={() => { clicksNotShown.current++; }}>
          bump the ref (nothing happens)
        </button>{" "}
        <button onClick={() => setCount((c) => c + 1)}>
          bump state (re-renders, revealing the ref)
        </button>
      </p>

      <h4>State, and the previous value kept in a ref</h4>
      <p style={{ fontSize: 14 }}>
        count: <strong>{count}</strong> · previous:{" "}
        <strong>{previousCount === undefined ? "—" : previousCount}</strong>
      </p>
      <p>
        <button onClick={() => setRunning((r) => !r)}>
          {running ? "stop" : "start"} the interval
        </button>
      </p>

      <p style={{ color: "#666", fontSize: 13 }}>
        Press the ref button several times — the render count does not move and
        neither does the displayed number. Then press the state button once and
        every hidden increment appears at the same moment.
      </p>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 30 of 119 decoded in the React.js track. One more won't hurt.

Back to track