React suspense from a practical point of view

One of the hottest topic in React world is suspense. Officially it supports lazy components for now, but in the future it could be used for more things, like data fetching, which, as you can find in the docs, you could test in experimental React version.

But the truth is, you can use suspense for anything already, right now, with standard and official React version!

What React suspense is?

Before we go on though, let's explain what exactly suspense is. Well, like the name suggests, this is a way to... suspend rendering of components, until something happens, after which rendering could be continued. That's it!

How does it work in practice? After taking example from React docs:

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

you can see that OtherComponent uses React.lazy, which triggers suspense on loading. You need to catch this with a dedicated Suspense component, which could render fallback component to show loading state.

What's interesting, Suspence component is similar to catching errors, it is like catching pending state from the top.

Ok, we know how suspence works now, but how to use it with other context? Let's see how!

Using suspense in any context

I am not sure why this isn't described in React the docs officially, but it turns out, that you can trigger suspense by... throwing a promise! Then, once resolved, React component will continue to render. This approach is already used by some popular libraries, like react-i18next.

How does it work? Extremely simple! Let's create a promised based sleep function which will resolve after a second:

import React, { Suspense } from 'react';

let loaded = false; // this is just for demonstration

const sleep = () => new Promise(resolve => { 
  setTimeout(() => {
    loaded = true;
    resolve();
  }, 1000);
});



function OtherComponent() {
  if (!loaded) {
    throw sleep();
  }

  return <div>Component loaded</div>;
}

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

How will it work? Initially loaded is false, so OtherComponent will throw promise. Then, fallback component will be rendered for 1000ms, after which promise will be resolved and finally Component loaded will be rendered.

This example is for demonstration, but this can be used for more things, like data fetching, or... anything! But there is one thing, which can benefit extremely from suspence - server side rendering!

Suspense based server side rendering

Officially suspense is not yet supported by React on the server side, however there is a nice temporary bridge which allows that - react-async-ssr.

Ok, but why do we care? For people experienced with SSR, you probably know how difficult this topic is. At first glance, it is simple, it is just rendering the very same components on the server. The issue is, that for most apps you make AJAX requests to get data. You need to await those before rendering. Before suspense, there were many strategies for this, like:

  • double render on the server, with 1st render triggering requests, then, once requests are finished, render 2nd time with data - this causes performance issues
  • attach static methods to components to make requests before rendering - this is hard to setup, leads to code duplication and issues with data fetching not from top level components

The problem with those methods is, that double rendering is performance problematic, while attaching static methods is like code duplication, not to mention that you needed to somehow describe which components' static methods should be called.

With suspense though, it looks like that:

  1. just render on the server
  2. once requests are fired, suspense is triggered
  3. once requests are finished, rendering continues
  4. 2 i 3 can be repeated many times
  5. rendering finishes.

This works surprisingly well, and according to me, is the best way to render apps on the server. You can write code without thinking about SSR, you can migrate not SSR apps without any code or architectural change, finally truly universal apps!

If you are interested, how this could work in practice, you could check this guide. In recent release of redux-requests, I added suspense-based SSR to the core and it works surprisingly well!

Happy suspending!