Skip to the solution
HardFrontend7 min read

What does the use() hook do, and why is it special?

528 views

The problem

Question presented to candidate: "use() is called a hook but it does not follow the Rules of Hooks. What is it and why is it different?"

What a strong answer should cover:

  • use() reads a resource β€” a promise or a context β€” during render. With a promise it suspends until the promise settles; with a context it does what useContext does.
  • It is the only hook that may be called conditionally, in a loop, or inside an early return. React documents this explicitly.
  • Why it can: it does not hold state on the fiber. Ordinary hooks are matched by position in a linked list, so a conditional call shifts every later hook onto the wrong record. use() reads something that already exists, so there is no slot to misalign.
  • With a promise, the promise must be created outside render β€” a promise created during render is a new one every attempt and never settles.
  • A rejected promise read with use() throws during render, so an error boundary catches it. Pair Suspense (pending) with an error boundary (failed).
  • It is what makes Server Components able to pass a promise to a Client Component and have it awaited during render.
  • It is not a data-fetching library β€” no caching, no deduplication, no request lifecycle. It only unwraps.

Clarifying questions expected:

  • "Where is the promise created?" β€” that is the correctness question.
  • "Is there an error boundary above this, as well as a Suspense boundary?"

Code / implementation expected: Optional. Showing a conditional use() next to a conditional useContext makes the point immediately.

  • react
  • suspense
  • data-fetching

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

7 min read Β· 10 sections

Try it in the playground

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

Reactuse() called conditionally, reading both a context and a promise
import { useState, useContext, use, createContext, Suspense, Component } from "react";

const Theme = createContext("light");

// Created OUTSIDE render. A promise created in the component body would be a
// new promise on every render attempt, and would never settle.
const messageP = new Promise((r) => setTimeout(() => r("resolved value"), 1200));
const failingP = new Promise((_, reject) =>
  setTimeout(() => reject(new Error("the request failed")), 800));
failingP.catch(() => {});                    // keep the console quiet

class Boundary extends Component {
  state = { error: null };
  static getDerivedStateFromError(error) { return { error }; }
  render() {
    if (this.state.error) {
      return <Box tone="bad">error boundary caught: {this.state.error.message}</Box>;
    }
    return this.props.children;
  }
}

// βœ… use() inside a condition, with a hook AFTER it β€” the arrangement that
//    would corrupt an ordinary hook's slot. React allows exactly this.
function Conditional({ on }) {
  const [before] = useState("before");
  let theme = "(not read)";
  if (on) theme = use(Theme);
  const [after] = useState("after");
  return <Box>{before} Β· theme={theme} Β· {after}</Box>;
}

// ❌ The same shape with useContext. Toggling this logs
//    "React has detected a change in the order of Hooks" to the console.
function ConditionalBad({ on }) {
  const [before] = useState("before");
  let theme = "(not read)";
  if (on) theme = useContext(Theme);
  const [after] = useState("after");
  return <Box tone="warn">{before} Β· theme={theme} Β· {after}</Box>;
}

function Message() { return <Box tone="good">use(promise) β†’ {use(messageP)}</Box>; }
function Failing() { return <Box>{use(failingP)}</Box>; }

function Box({ children, tone }) {
  const bg = tone === "bad" ? "#fdf0f0" : tone === "good" ? "#f2f9f2" : tone === "warn" ? "#fff8e6" : "#f6f6f8";
  return (
    <div style={{ background: bg, border: "1px solid #ddd", borderRadius: 6, padding: 10, fontSize: 13, marginBottom: 8 }}>
      {children}
    </div>
  );
}

export default function App() {
  const [on, setOn] = useState(false);

  return (
    <Theme.Provider value="dark">
      <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 520 }}>
        <button onClick={() => setOn((v) => !v)} style={{ marginBottom: 10 }}>
          condition is {String(on)} β€” toggle it
        </button>

        <Conditional on={on} />
        <ConditionalBad on={on} />

        <Suspense fallback={<Box>suspended β€” waiting for the promise…</Box>}>
          <Message />
        </Suspense>

        {/* A rejection throws DURING render, so it needs an error boundary as
            well as a Suspense boundary. Suspense alone would not catch it. */}
        <Boundary>
          <Suspense fallback={<Box>suspended β€” waiting for the failing one…</Box>}>
            <Failing />
          </Suspense>
        </Boundary>

        <p style={{ fontSize: 13, color: "#666" }}>
          Toggle the condition with the console open. The first row changes the
          number of hooks it calls and React says nothing. The second does the
          same thing with <code>useContext</code> and React warns that the hook
          order changed β€” that is the difference the exception is about.
        </p>
      </div>
    </Theme.Provider>
  );
}

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 99 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 nextHard

What do `startTransition` and `useTransition` actually do under the hood?

Continue