Introduction
If you’ve been following the React community, then you’ll know about a feature called ‘Suspense’. This is still an experimental feature (for data fetching), but essentially it allows you to ‘wait’ for some code to load and specify a loading state such as a spinner whilst we’re waiting.
Here’s the example that the React documentation gives us:
const ProfilePage = React.lazy(() => import("./ProfilePage")) // Lazy-loaded
// Show a spinner while the profile is loading
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>
In simple terms, this allows your components to wait for a condition to be true until they render. Most people imagine this in the context of fetching data, but it equally applies to things like images, scripts, or anything asynchronous.
What’s the point?
Currently, we fetch data in a number of ways - such as fetching on render. This is typically achieved through useEffect()
and performing our fetch operation within the effect.
This means that our component renders, and the fetch operation doesn’t start till that render completes. If other components on your page depend on data being present, then this cascades down your application. The code sample that React provides is a great illustration:
function ProfilePage() {
const [user, setUser] = useState(null)
useEffect(() => {
fetchUser().then(u => setUser(u))
}, [])
if (user === null) {
return <p>Loading profile...</p>
}
return (
<>
<h1>{user.name}</h1>
<ProfileTimeline />
</>
)
}
function ProfileTimeline() {
const [posts, setPosts] = useState(null)
useEffect(() => {
fetchPosts().then(p => setPosts(p))
}, [])
if (posts === null) {
return <h2>Loading posts...</h2>
}
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
)
}
Here we are waiting for our user details to be fetched, then we start fetching the posts only after we have fetched the user details. Each of these takes time and the user is left waiting.
Or we could be clever and use something like GraphQL and fetch our data in a single step, but the user is still left waiting for this operation to complete. In the first example, we rendered, then fetched. With GraphQL, we start fetching, finish fetching, and then render.
With Suspense, we make a subtle change and instead we:
- Start fetching
- Start rendering
- Finish fetching
This speeds everything up as the user gets a response before we’ve finished grabbing all of our data.
The effect is to stream the response as data becomes available and make content available sooner, rather than waiting for all data to become available before displaying it.
Data that hasn’t been fetched yet simply falls back to a loading indicator. This has the added advantage that we can remove if(loading)
checks from our code.
How can I use React Suspense for data fetching?
The feature is currently experimental and shouldn’t be used on production systems (again, for data fetching - totally cool for lazy-loading components). If you want to play around with it however, you can easily install it via
yarn add react@experimental react-dom@experimental
This introduces Concurrent Mode which Suspense is a part of. There are some pretty big changes involved, including the way that you render your React application.
Instead of:
import ReactDOM from "react-dom"
ReactDOM.render(<App />, document.getElementById("root"))
you would now have this for your entry point:
import ReactDOM from "react-dom"
ReactDOM.createRoot(document.getElementById("root")).render(<App />)
The reason for this change is that Concurrent Mode is a pretty big change to how React works, and so you have to opt-in to it wholesale, rather than the ‘drop-in’ features of Fragments, or Context.
Conclusion
Concurrent Mode and React Suspense look amazing! It’s a big paradigm shift in how we think about data and our code, and I think that it will really change the way that we perceive the responsiveness of applications. From a DX perspective, this will lead to clearer code, and encourage us to load data closer to where it’s actually being used.
It’s going to be a little while until Concurrent Mode and Suspense are ready for production, but they’re already showing a lot of potential.