Suspense
Suspense
is a React component that displays a fallback until its children have finished loading.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
Usage
Displaying a fallback while something is loading
You can wrap any part of your application with a Suspense component. If either data or code in its children hasnât loaded yet, React will switch to rendering the fallback
prop instead. For example:
<>
<Post />
<Suspense fallback={<LoadingSpinner />}>
<Comments />
</Suspense>
</>
Suppose that Comments
takes longer to load than Post
. Without a Suspense boundary, React wouldnât be able to show either component until both have loaded â Post
would be blocked by Comments
.
Because of the Suspense boundary, Post
doesnât need to wait for Comments
. React renders LoadingSpinner
in its place. Once Comments
finishes loading, React replaces LoadingSpinner
with Comments
.
Suspense will never show unintentional âholesâ in your content. For example, if PhotoAlbums
has loaded but Notes
have not, with the structure below, it will still show a LoadingSpinner
instead of the entire Grid
:
<>
<ProfileHeader />
<Suspense fallback={<LoadingSpinner />}>
<Grid>
<PhotoAlbums />
<Notes />
</Grid>
</Suspense>
</>
To reveal nested content as it loads, you need to add more Suspense boundaries.
Revealing nested content as it loads
When a component suspends, it activates the fallback of only the nearest parent Suspense boundary. This means you can nest multiple Suspense boundaries to create a loading sequence. Each Suspense boundaryâs fallback will be filled in as the next level of content becomes available.
To illustrate, consider the following example:
<Suspense fallback={<BigSpinner />}>
<MainContent>
<Post />
<Suspense fallback={<CommentsGlimmer />}>
<Comments />
</Suspense>
</MainContent>
</Suspense>
The sequence will be:
- If
Post
hasnât loaded yet,BigSpinner
is shown in place of the entire main content area. - Once
Post
finishes loading,BigSpinner
is replaced by the main content. - If
Comments
hasnât loaded yet,CommentsGlimmer
is shown in its place. - Finally, once
Comments
finishes loading, it replacesCommentsGlimmer
.
Lazy-loading components with Suspense
The lazy
API is powered by Suspense. When you render a component imported with lazy
, it will suspend if it hasnât loaded yet. This allows you to display a loading indicator while your componentâs code is loading.
import { lazy, Suspense, useState } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
function MarkdownEditor() {
const [showPreview, setShowPreview] = useState(false);
// ...
return (
<>
...
{showPreview && (
<Suspense fallback={<Loading />}>
<h2>Preview</h2>
<MarkdownPreview />
</Suspense>
)}
</>
);
}
In this example, the code for MarkdownPreview
wonât be loaded until you attempt to render it. If MarkdownPreview
hasnât loaded yet, Loading
will be shown in its place. Try ticking the checkbox:
import { useState, Suspense, lazy } from 'react'; import Loading from './Loading.js'; const MarkdownPreview = lazy(() => delayForDemo(import('./MarkdownPreview.js'))); export default function MarkdownEditor() { const [showPreview, setShowPreview] = useState(false); const [markdown, setMarkdown] = useState('Hello, **world**!'); return ( <> <textarea value={markdown} onChange={e => setMarkdown(e.target.value)} /> <label> <input type="checkbox" checked={showPreview} onChange={e => setShowPreview(e.target.checked)} /> Show preview </label> <hr /> {showPreview && ( <Suspense fallback={<Loading />}> <h2>Preview</h2> <MarkdownPreview markdown={markdown} /> </Suspense> )} </> ); } // Add a fixed delay so you can see the loading state function delayForDemo(promise) { return new Promise(resolve => { setTimeout(resolve, 2000); }).then(() => promise); }
This demo loads with an artificial delay. The next time you untick and tick the checkbox, Preview
will be cached, so there will be no loading state displayed. To see the loading state again, click âResetâ on the sandbox.
Reference
Suspense
Props
children
: The actual UI you intend to render. Ifchildren
suspends while rendering, the Suspense boundary will switch to renderingfallback
.fallback
: An alternate UI to render in place of the actual UI if it has not finished loading. Any valid React node is accepted, though in practice, a fallback is a lightweight placeholder view, such as a loading spinner or skeleton. Suspense will automatically switch tofallback
whenchildren
suspends, and back tochildren
when the data is ready. Iffallback
suspends while rendering, it will activate the closest parent Suspense boundary.
Caveats
- React does not preserve any state for renders that got suspended before they were able to mount for the first time. When the component has loaded, React will retry rendering the suspended tree from scratch.
- If Suspense was displaying content for the tree, but then it suspended again, the
fallback
will be shown again unless the update causing it was caused bystartTransition
oruseDeferredValue
. - If React needs to hide the already visible content because it suspended again, it will clean up layout Effects in the content tree. When the content is ready to be shown again, React will fire the layout Effects again. This lets you make sure that Effects measuring the DOM layout donât try to do this while the content is hidden.
- React includes under-the-hood optimizations like Streaming Server Rendering and Selective Hydration that are integrated with Suspense. Read an architectural overview and watch a technical talk to learn more.
Troubleshooting
How do I prevent the UI from being replaced by a fallback during an update?
Replacing visible UI with a fallback creates a jarring user experience. This can happen when an update causes a component to suspend, and the nearest Suspense boundary is already showing content to the user.
To prevent this from happening, mark the update as non-urgent using startTransition
. During a transition, React will wait until enough data has loaded to prevent an unwanted fallback from appearing:
function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
This will avoid hiding existing content. However, any newly rendered Suspense
boundaries will still immediately display fallbacks to avoid blocking the UI and let the user see the content as it becomes available.
React will only prevent unwanted fallbacks during non-urgent updates. It will not delay a render if itâs the result of an urgent update. You must opt in with an API like startTransition
or useDeferredValue
.
If your router is integrated with Suspense, it should wrap its updates into startTransition
automatically.