Engrain by ByteRush

RCT.05.02 Mid-level· 12 minCore — asked often

use, Suspense and error boundaries: loading and failure as part of the tree

Read promises with use() inside Suspense boundaries, keep promises stable, nest boundaries for independent loading, catch failures with error boundaries, hide UI while keeping state with Activity, and avoid fallbacks with transitions.

Comes up in: React, Frontend system design

Learn#

Kunal’s dashboard shows one big spinner until every widget’s data has loaded, and if the offers service fails, the whole page shows an error. One slow or broken widget holds the others hostage.

Suspense lets a component wait for data while React shows a fallback. A Suspense boundary, <Suspense fallback={…}>, catches waiting children. An error boundary catches failures. Wrap each independent area in both, and each loads and fails on its own:

import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register(); // a DOM for Node; in a browser you already have one
const { createElement: h, use, Suspense, Component } = await import("react");
const { createRoot } = await import("react-dom/client");

const wait = (ms) => new Promise((r) => setTimeout(r, ms));
// Promises are created once, outside render, so every render reads the same promise.
const orders = wait(80).then(() => ["Tea", "Rice"]);
const offers = wait(40).then(() => { throw new Error("offers service down"); });
offers.catch(() => {}); // the error boundary below handles it

class ErrorBoundary extends Component {
  state = { error: null };
  static getDerivedStateFromError(error) { return { error }; }
  render() { return this.state.error ? h("p", null, `Offers unavailable (${this.state.error.message})`) : this.props.children; }
}
function Orders() { return h("p", null, `Orders: ${use(orders).join(", ")}`); }
function Offers() { return h("p", null, `Offers: ${use(offers).length}`); }

const box = document.createElement("div");
const root = createRoot(box, { onCaughtError: () => {} }); // keep the log quiet
root.render(h("div", null,
  h(Suspense, { fallback: h("p", null, "Loading orders…") }, h(Orders)),
  h(ErrorBoundary, null, h(Suspense, { fallback: h("p", null, "Loading offers…") }, h(Offers)))));
const show = (label) => console.log(label, [...box.querySelectorAll("p")].map((p) => p.textContent).join(" | "));
await wait(10); show("at first:");
await wait(800); show("later:   ");
root.unmount();
This example uses Node.js, so it runs in our checks rather than in your browser.

Output (verified)

at first: Loading orders… | Loading offers…
later:    Orders: Tea, Rice | Offers unavailable (offers service down)

use(promise) reads a promise’s value during render. The promise must be stable: created once (by a framework, a cache, or outside the component), never in render.

Two independent boundariesStep 1 of 4

Page

  • Loading orders…
  • Loading offers…

State

    empty

    Orders calls use(orders). The promise is still pending, so Orders suspends and the nearest Suspense shows its fallback.

    Read the steps as text
    1. Orders calls use(orders). The promise is still pending, so Orders suspends and the nearest Suspense shows its fallback.
    2. The offers promise rejects. use() throws the error, and the nearest error boundary catches it.
    3. The orders promise resolves. React retries Orders, which now returns the data.
    4. Each area shows its own result: one loaded, one failed with a message. Neither blocked the other.

    Predict#

    Predict first

    A component does const data = use(fetch('/api/orders').then(r => r.json())). What happens?

    Interview angle#

    Say it like this — about 30 seconds

    “use reads a promise or a context during render. With a promise, the component suspends until it settles; the nearest Suspense boundary shows its fallback, and the nearest error boundary catches rejections. Unlike other hooks, use can be called conditionally. The promise must be stable, created by a framework, a cache or a server component, not inside render. I nest boundaries so independent parts load and fail separately. Error boundaries are still class components or a library.”

    Stays on your device. Nothing is uploaded.
    Follow-up What does the use hook do? How is it different from other hooks?
    It reads a promise or context during render, suspending for promises. It may be called in conditions and loops.
    Follow-up How do Suspense and error boundaries work together?
    Suspense handles the waiting state; the error boundary above it handles rejection. Together they cover loading, success and failure for one area.
    Follow-up How do you avoid replacing visible content with a fallback on navigation?
    Start the update in a transition; React keeps the old UI until the new one is ready.

    Practice#

    On your own

    Write the promise cache that makes use() and Suspense safe, and the “throw the promise” rule Suspense is built on.

    Done when

    • get returns the same promise for the same key and fetches each key once.
    • read throws the pending promise, returns the value, or throws the error.
    • Different keys are independent.
    Your solution
    Ctrl/⌘ + Enter

    Remember#

    Try to answer each card before you open it. Then add them to your reviews.

    What does use(promise) do while the promise is pending?

    Suspends the component; the nearest Suspense boundary shows its fallback.

    Why must the promise passed to use be stable?

    A new promise each render suspends again forever.

    Which catches a rejected promise read with use?

    The nearest error boundary.

    What does Activity do?

    Hides a part of the UI while keeping its state, and pauses its effects.

    Go deeper

    Four layers. Stop at the one your interview needs; each ends with a quick check.

    The core ideaEvery round

    use is the one hook-like API that can be called in conditions and loops. It also reads context: use(ThemeContext) Source: React: use.

    Check yourself: Can use() be called inside an if?

    Yes, unlike other hooks.

    How it works underneathSenior rounds
    Suspending components throw to the nearest boundary. Nested boundaries reveal from the outside in, and React throttles reveals by a few hundred milliseconds, so content does not pop in piece by piece, as in the example’s timing.
    Check yourself: Why does React keep a fallback visible a little longer sometimes?

    It throttles reveals so content does not pop in piece by piece.

    Edge cases and trapsWhere people slip
    Error boundaries catch render errors and rejected use reads below them, not errors in event handlers or effects’ async code. There is no hook version yet; use a class or react-error-boundary.
    Updates started in a transition keep showing old content instead of a fallback.
    Check yourself: Do error boundaries catch errors in event handlers?

    No. Only during render, in lifecycle methods and in suspended reads.

    In productionStaff-level answers
    Activity, <Activity mode="hidden">, hides a subtree but keeps its state and pauses its effects, useful for tabs and pre-rendering the next screen (added in React 19.2) Source: React: Activity.
    Check yourself: Which React version added Activity?

    React 19.2.

    Finished the five steps? Mark it, and its cards join your reviews.

    Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

    Interview questions this answers

    • What does the use hook do? How is it different from other hooks?
    • How do Suspense and error boundaries work together?