Copied from the React docs.
// This is not a Promise. It's a special object from our Suspense integration.
const resource = fetchProfileData();
function ProfilePage() {
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}
function ProfileDetails() {
// Try to read user info, although it might not have loaded yet
const user = resource.user.read(); return <h1>{user.name}</h1>;
}
function ProfileTimeline() {
// Try to read posts, although they might not have loaded yet
const posts = resource.posts.read(); return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
);
}
Here’s what happens when we render <ProfilePage>
on the screen:
fetchProfileData()
. It gives us a special “resource” instead of a Promise.<ProfilePage>
. It returns <ProfileDetails>
and <ProfileTimeline>
as children.<ProfileDetails>
. It calls resource.user.read()
. None of the data is fetched yet, so this component “suspends”. React skips over it, and tries rendering other components in the tree.<ProfileTimeline>
. It calls resource.posts.read()
. Again, there’s no data yet, so this component also “suspends”. React skips over it too, and tries rendering other components in the tree.<ProfileDetails>
suspended, React shows the closest <Suspense>
fallback above it in the tree: <h1>Loading profile...</h1>
. We’re done for now.This resource
object represents the data that isn’t there yet, but might eventually get loaded. When we call read()
, we either get the data, or the component “suspends”.
As more data streams in, React will retry rendering, and each time it might be able to progress “deeper”. When resource.user
is fetched, the <ProfileDetails>
component will render successfully and we’ll no longer need the <h1>Loading profile...</h1>
fallback. Eventually, we’ll get all the data, and there will be no fallbacks on the screen.
This has an interesting implication. Even if we use a GraphQL client that collects all data requirements in a single request, streaming the response lets us show more content sooner. Because we render-as-we-fetch (as opposed to after fetching), if user
appears in the response earlier than posts
, we’ll be able to “unlock” the outer <Suspense>
boundary before the response even finishes.
All of this results in a UI that avoids waterfall-ing and setting dependencies. Data isn't reliant on other data retrieval, nor is it dependent on the component being rendered. This achieves a different UX for the user. Individual parts of the page render as soon as they can, rather than the entire page rendering at once.