Select Page

Suspense in React 18: How it works, and how you can use it

Published: October 9, 2022

What’s happening here, is the line of code const specialPromiseResource = getSpecialPromiseTofetchCities() is executed outside of our Suspense component, and it requests the data from the outside service or REST call. The call then returns immediately, and most importantly, returns with only a special resource reference to a promise about this particular data retrieval call.

Looking into our updated React CityList component, notice that we no longer have our programmatic calls that get executed after the page renders, that is the code passed into useEffect. Instead, we call resource.readData() and assume — for the sake of this component  — that when data is returned, we can immediately render that data without being concerned about loading states.

What makes this work is the code inside our specialPromiseResource method is using special capabilities that cause our CityList component to render, when (and only when) the promise to get the city list data is complete. You can think about the call inside the CityList component — that is the line const data = resource.readData() — as helping React Concurrent Rendering figure out if it’s time to render the city list to the UI.

The beauty of this code is that building components now to render data is much simpler. We don’t need to worry about tracking loading state changes like we had to do before we had Suspense.

You might be thinking that this doesn’t seem that different from the code we build prior to Suspense and Concurrent Rendering! That’s probably true, but in this example, our particular React component only has to deal with one outside call to a single data source. 

Think about the case of a complex web site, where a page may include data from many different external sources. The idea is that we set up all our special promise calls to get data, and then as the page renders and these external calls complete, different parts of the page render. 

With our new Suspense implementation, the React Concurrent rendering engine can optimally figure out how to re-render the page based on what data arrives first, while at the same time, only rendering what is necessary to our browser DOM.

Imperative versus Declarative

From a user’s perspective, regardless of whether or not we use Concurrent Rendering, the result in the browser will basically be the same. The browser UI first displays a loading message, followed shortly by the UI updating to show the retrieved data.

The difference to us — as developers and designers — is significant. Without Suspense we have to programmatically track our loading and error states of all of our components. For a single external data call (like we have in our example above with our list of cities) the problem is not hard. However, when our component hierarchies get more complex, and we have multiple dependencies, tracking the states can get really confusing. 

This type of code where we explicitly track and render based on things like state values we typically refer to as imperative programming — meaning we code exactly what we want, in the order we want it, with all the required conditionals along the way.

Declarative programming, on the other hand, is typically easier to write. It means instead of having to figure out what order everything should be done in, we state (or declare) our intention of what we want, and we let the application (or in our case, the new Concurrent Rendering engine) figure it out for us.

Coding with useEffect and isLoading as a declared state can be thought of as imperative programming, and coding with Suspense and fallback UIs can be thought of as declarative programming.

The Elephant in the Room

Ever hear the term “The Elephant in the Room?” It’s when there’s something important that needs to be discussed that everyone is aware of, but nobody wants to. Unfortunately, the latest version of React has one of those.

Here’s the elephant: In the case of React 18 Concurrent Rendering and Suspense, Suspense is fully released for production, but you can’t use it unless you program with a very opinionated and somewhat advanced programming API Relay for your data retrieval.

The reason the Facebook React team includes Suspense and Concurrent Rendering in React 18 is it is 100% production ready if you use Relay. As proof of that, it’s currently what is driving, probably the highest volume and likely the most used site on the internet. If you want the benefits of Suspense and Concurrent Rendering now, you can build your apps with Relay.

If, however, you are like most of us developers and are using one of the other data access libraries (like SWR, React-Query, or even ApolloGraphQL) you will not be able to use Suspense at the moment. 

The React team has made clear that their intention is to make Suspense work with APIs beyond Relay. Unfortunately for now, we just need to wait and make sure whatever API we use is production-ready for Suspense use. 

Once the React team figures out a good way to integrate other API’s into Suspense — and those teams do the implementations — everyone will be able to migrate to Suspense.

How to Learn to Program Suspense Now

The React team has published documentation that includes a very good example of how to use Suspense similar to what I did at the beginning of this post. Thinking about the pseudo code above where I call const specialPromiseResource = getSpecialPromiseTofetchCities() to get our special promise resource to be used in our Suspense wrapped component, as well as the call const data = resource.readData() in the data rendering component itself, it creates a working example with a “not for production” sample implementation of the necessary resources.

If you are thinking “Maybe I can use this code, and cut and paste it into my own apps and release them to production”, you should really think again. You’ll find yourself copying these lines of commented-out code along with the rest!