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();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.
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
- Orders calls use(orders). The promise is still pending, so Orders suspends and the nearest Suspense shows its fallback.
- The offers promise rejects. use() throws the error, and the nearest error boundary catches it.
- The orders promise resolves. React retries Orders, which now returns the data.
- 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?
Each render calls fetch again, so use always sees a new pending promise and suspends forever. Create the promise once: in a Server Component, a framework loader, or a cache keyed by URL. This is the most common use mistake.
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.”
Follow-up What does the use hook do? How is it different from other hooks?
Follow-up How do Suspense and error boundaries work together?
Follow-up How do you avoid replacing visible content with a fallback on navigation?
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
getreturns the same promise for the same key and fetches each key once.readthrows the pending promise, returns the value, or throws the error.- Different keys are independent.
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
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
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.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 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
usehook do? How is it different from other hooks? - How do Suspense and error boundaries work together?