skip to content

Fallback Semantics and Placement

Where you put a boundary decides how much of the screen disappears while something loads, and transitions let you avoid showing a fallback at all on updates. A classic question is why an existing UI suddenly flashed back to a spinner.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In React, what does the fallback prop of <Suspense> render, and what decides when the user sees the fallback instead of the boundary's children?

level: juniorimportance: must knowfreq 70%

answer

  1. shown in place of the children
  2. any React node, even null
  3. all children wait together
  4. one commit, no half-drawn tree
  5. no boundary, no default spinner

basics

~20 s

The fallback prop holds the UI React shows in place of a <Suspense> boundary's children while something inside those children is not ready yet. React swaps the real children back in as soon as nothing inside that boundary is suspended.

solid answer

~50 s

`<Suspense>` takes a `fallback` prop and a subtree of children. While anything inside that subtree is suspended — a `React.lazy` component whose code has not arrived, or a component reading data that is not available yet — React renders `fallback` instead of the children. The swap is all-or-nothing for that boundary: if three siblings sit under one boundary and only one of them is suspended, the user sees the fallback and none of the three. Once nothing inside the boundary is suspended any more, React commits the real children and removes the fallback in one step, so you never see a half-drawn subtree. `fallback` accepts any React node — an element, a string, even `null` — and it is a real render, so keep it cheap and stateless. There is no implicit root fallback: a component that suspends with no boundary above it is an error.

go deeper

for a junior

Know that <Suspense> takes a fallback prop, that the fallback replaces everything inside the boundary while any of it is loading, and that the real children come back once nothing inside is still waiting.

for a middle

Be ready to explain the all-or-nothing rule with a concrete example and to say what happens when no boundary exists above a suspending component. Mention that the fallback is a real render whose state is discarded on the swap.

for a senior

Show that you treat the fallback as a design decision: how much screen it covers, how cheap it is to render, and whether an empty fallback is better than a spinner for a peripheral widget. Explain why loading UI belongs to the layout owner rather than the leaf component.

for a principal

Own the convention: where the backstop boundary lives so no suspension can ever reach the root unhandled, what the house skeleton vocabulary is, and how teams avoid every component reinventing its own loading UI.

## What a boundary is `<Suspense>` is an ordinary React component with one special prop, `fallback`. Everything you nest inside it is the real UI. `fallback` is the placeholder React puts on screen in that subtree's place while something inside it is not ready. ```jsx <Suspense fallback={<p>Loading chart…</p>}> <RevenueChart /> </Suspense> ``` Nothing about `RevenueChart` changes. The boundary is a declaration about the *hole in the screen*: "if anything under here isn't ready, show this instead." ## What "not ready" means A component becomes suspended when it tries to render something that has not arrived yet. In React 19 the two everyday sources are a component created with `React.lazy` whose module has not loaded, and a component that reads data that is still pending — a Server Component awaiting its data, or a client component reading a resource that has not settled. From the boundary's point of view the cause does not matter: any suspension inside its subtree puts it into fallback state. ## All-or-nothing within one boundary This is the part candidates get wrong. A boundary does not render the children that are ready and a fallback for the one that is not. It renders `fallback` *instead of the entire subtree*. ```jsx <Suspense fallback={<Skeleton />}> <Header /> {/* ready */} <Feed /> {/* suspended */} <Footer /> {/* ready */} </Suspense> ``` The user sees `<Skeleton />` and none of the three. `Header` and `Footer` appear only when `Feed` is ready too. This is deliberate: React reveals a consistent screen rather than a progressively assembling one, and it is exactly why boundary placement matters — the boundary defines how much of the page waits on the slowest thing inside it. A boundary reacts only to suspensions *inside itself*. A sibling boundary elsewhere in the tree going into fallback has no effect on it. ## The fallback is a normal render `fallback` accepts any React node. `fallback={<Spinner />}`, `fallback="Loading…"` and `fallback={null}` are all legal; `null` means "render nothing here while we wait", which is occasionally what you want for a small, non-blocking widget. Because it is a normal render, the fallback's components genuinely mount when the boundary suspends and unmount when the content is revealed. Two consequences follow. First, any state inside the fallback is thrown away on the swap — never store anything meaningful there. Second, an expensive fallback costs real render time at exactly the moment the app is already waiting, so a fallback should be cheap: a skeleton, a spinner, a placeholder box. ## The swap back When the last suspended thing inside the boundary resolves, React renders the children and removes the fallback in the same commit. There is no intermediate frame where half the children are on screen; the transition from placeholder to content happens in one paint. ## There is no default fallback A suspension travels up the tree until it finds a boundary. If there is none, React has nothing to render in its place, so it raises an error telling you to add a `<Suspense fallback=…>` higher in the tree. React never invents a spinner for you and never silently blanks the app. In practice this means every code path that can suspend must have a boundary somewhere above it — which is why frameworks and shells usually keep at least one boundary near the root as a backstop, even when the interesting boundaries are much deeper. ## What this buys you The payoff is that loading state stops being a per-component `if (loading) return <Spinner/>` branch scattered through your code. The component that needs data just reads it, and an ancestor — often owned by whoever owns the layout — decides what the waiting UI looks like and how much of the screen it covers. Loading UI becomes a property of the layout instead of a property of every leaf component.

  • Can the fallback prop be null, and when would you want that?
    Yes — `fallback={null}` is legal and renders nothing while the subtree is suspended. It is reasonable for a small, non-essential widget where a spinner would be more distracting than an empty space, and for a boundary whose only job is to stop a suspension from reaching an ancestor whose fallback would blank out far more of the screen.
  • Two sibling components under the same boundary each load data. One resolves in 50 ms and the other in two seconds. When does the fast one appear?
    After two seconds, with the slow one. A boundary is all-or-nothing: while anything inside it is suspended, the whole subtree is replaced by the fallback. If you want the fast component on screen at 50 ms, it needs to be outside that boundary or inside its own boundary.
  • Does a component that suspends lose the state it had before?
    Not if it had already rendered. When an already-revealed boundary suspends again, React hides its children rather than unmounting them, so their state survives and comes back when the content is revealed. State is only lost when the components genuinely unmount, for example when the fallback's own components are swapped out.

saying these in an interview costs you the question

  • Thinks ready siblings render while only the pending one is replaced
  • Believes React shows a built-in spinner when no boundary exists
  • Puts state or data fetching inside the fallback element
  • Thinks fallback must be a single spinner component, not any node
  • Assumes a suspension is caught by every boundary in the tree

context

open as a page

When a React component suspends, which <Suspense> boundary shows its fallback, and how does nesting boundaries change how much of the screen is replaced?

level: middleimportance: must knowfreq 62%

basics

~20 s

The nearest <Suspense> boundary above the suspended component shows its fallback; boundaries further up are unaffected. Nesting boundaries therefore shrinks the blast radius, letting an inner section swap to a small placeholder while everything outside it stays on screen.

open as a page

A React list that is already on screen flips back to its <Suspense> fallback every time the user changes a filter that triggers a new data read. Why does the revealed content disappear, and how do you keep it visible while the new data loads?

level: seniorimportance: must knowfreq 58%

basics

~20 s

The filter change is an urgent update, so re-suspending the boundary immediately swaps the revealed list for its fallback. Marking that update as a transition with startTransition or useTransition tells React to keep the old content on screen until the new data is ready.

open as a page

How do you decide where to place <Suspense> boundaries across a React page, and what goes wrong at each extreme — one boundary around everything versus a boundary around every component?

level: principalimportance: should knowfreq 40%

basics

~20 s

Place a boundary where a placeholder can replace a region without moving anything around it, and where that region's data genuinely arrives at a different time from its neighbours. One boundary makes the whole page wait on its slowest part; a boundary per component produces a flickering mosaic that reflows repeatedly.

open as a page