Skip to the solution
MediumFrontend6 min read

Describe the useRef hook and its typical use cases.

1.1k views

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.

  • hooks
  • useref
  • dom manipulation
  • performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactA DOM ref and an instance-value ref, with the render count standing still
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>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 30 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextMedium

How does React handle security concerns like XSS?

Continue