Trying to understand React Suspense

Xun Ding
6 min readDec 12, 2021
Some reading note about React suspense
Source: dreamstime.com

Being a developer always feels like sailing on a sea teeming with debates and discussions, ideas and methodologies. A few years of working in the field make it enough to see a long tailed graveyard of dead or half-dead attempts, libraries and frameworks. Darn it, who is still using Sencha’s Ext Js? Angular Js 1.x likely won’t get you a job interview. Is Ember Js still a thing? The law of make it or die.

Still you sail, riding the current of your time. Most of the time, you just want to get the job done, building that tiny piece of code that would become a component, which in turn becomes a part of an intricate knotted web or system or something that is so large, complicated and unrecognizable that you forget that you actually have anything to do with it. Or maybe you will be so proud you think you own it.

Sometimes you pause. Inspect and ponder whatever ideas washed over your way (or sent over by your boss). For example, React Suspense (the term itself is enough to suspend me mid work, wondering: what the heck is this suspense?).

For the long of it, you can google and digest the 9 million plus and growing search results; for the short and perfunctory and bullet points of it, it is the below:

  1. It is unofficially introduced in React 16.6 around December 2018. Fast forward to now (2021, the year of React 17.x) , it is still unofficial and experimental; It will be part of React 18.
  2. Suspense allows you or your components to render while waiting for the data to be available. A teeny bit like your old timer loading but more structured and systematic, with SSR (sever side rendering) support ;
  3. It is data fetching agnostic; it does not care how you fetch your data, GraphQL, Rest or whatever.
  4. It helps you avoid race conditions; it allows you to fetch data and render components as soon as possible.

Three approaches of data fetching

So why the need for such thing as React suspense?

For any web applications, if HTML is the body, skin and skeleton — the structure and mark-up, CSS is the clothes, shoes and makeup — the style and flavor, then data is the blood coursing through the entirety of it, carrying oxygen and nutrients, basically life.

The central question is how, when you fetch and deliver your data and how soon the various parts of your application can spring to life and be interactive.

There are three basic approaches:

  1. Fetch-on-render

A system is composed of a set of components. Each component has logic for data fetching. As soon as components are rendered, they will fetch the data for their own needs.

This has been the default mode in the client-centric, rest-api-dominating web apps.

React way:

useEffect(() => {
fetchSomething();
}, []);

Vue way (just to refresh my hardly-left-memory of Vue js):

created () {
// fetch the data when the view is created
this.fetchSomething()
},

This is dubbed as the waterfall mode. As in that, because of the hierarchical nature of components make up, data is often fetched in stages and causes some unnecessary delay. For example, if you are displaying a list of artists each with their body of works, you often have to fetch the information of the artists first, then fetch the list of work by an artist, then fetch the detailed info for any given work.

It somehow reminds me of that bone song:

your head bone connected from your neck bone
Your neck bone connected from your shoulder bone
Your shoulder bone connected from your back bone
Your back bone connected from your hip bone
...

Bit by bit, you fetch the data; component by component, it loads.

2. Fetch-then-render

A different approach is fetch all of the data you need then render your components. This has been the default approach for the server side rendering (SSR) web applications. In Next js, data needed for a page is usually fetched via a getServerSideProps method:

async function getServerSideProps() {
const [artists, works] = await Promise.all([
fetchArtists(),
fetchWorks()
]);
return {
props: {
users,
works,
},
};
}

This solves the above cascading delay issue. However the drawback is we get an all-or-nothing situation. Until all data is fetched and nothing gets to even start to render. More on this later.

We can set the same thing up without SSR rendering, basically load all data in one big data requests, then pass the data as props to individual components.

3. Render-as-you-fetch using Suspense

As a story goes, you introduce your obstacles and dilemmas, you show your heroic or dumb ways trying to overcome, then, boom, Suspense! You triumph and go home and watch Netflix.

So neither of the above two approaches offers a great loading experience for users. As soon as user requests a page, you need to load the data as early as possible and show the results as soon as possible. To have the best of the two, you have to parallelize both the data fetching and view rendering, while still allowing prioritization of some over others; You also need to abstract away much of the implementation details away, so it is clean and easy to use.

Here comes suspense.

Some code snippet (from the aforementioned article):

function Article() {
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}

Suspense for SSR

The much anticipated React 18 is on the horizon and offers a new Suspense SSR Architecture. Because, as Dan Abramov argued in great detail and reasoning, currently all SSR frameworks suffer from the following drawbacks: (keyword: Waterfall).

  1. Data blocking: data has to be fully fetched before the page can be loaded

This has caused a lot of grief. Unless you have very good caching implemented or your little page is super light-weighted, every page navigation would seem like nothing is working until the server data comes back).

2. JavaScript blocking: everything has to be loaded before hydration

Hydration is process unique to SSR apps. Currently any web apps are either client-side rendered (CSR) or server-side rendered (SSR). CSRs are utterly and hopelessly dependent on JavaScript. Without JavaScript being fully loaded (which usually takes a while), nothing works. On the other hand, SSRs generates static HTML on the server side and therefore everyone can have at least a rudimentary glimpse of your page (especially if it is a page full of content), JavaScript or not. However, static HTML would not respond to your mouse clicks and keyboard stroke, or your scroll and swipes. This is where hydration comes into play.

Hydration is to SSR apps as water is to your lawns, as says in this article:

This process of rendering your components and attaching event handlers is known as “hydration”. It’s like watering the “dry” HTML with the “water” of interactivity and event handlers.

Hydration does not happen unless static html is sent to the user and all JavaScripts loads on the client side. Because, as the rule goes, like a gardener, React keeps a mental map of all of the components and have to match up all of them when it hydrates (i.e, attaching event handlers to) your fancy Dom tree.

3. Hydration blocking: hydration has to be completed before anything becomes interactive

React hydration is a highly efficient yet highly blocking process. It is all-or-nothing. Unless it finishes hydrating all of the components, from header to footer, your app will be too busy to even respond to a single click on your all shiny brand logo .

With all of these glaring problems, React 18 has provided suspense as the ultimate answer.

Code Samples:

I haven’t started to build my own code yet (will do at a later time. Maybe wishful thinking). But there are always ingenious and generous developers that have shared their great code (see below).

React / Next demo:

React SSR suspense demo:

Reading List:

--

--