Eduardo Reveles

React Suspense, lazy() & Concurrent Mode

Back in March, Dan Abramov presented a new feature (in development) for React called Suspense, which allows you to defer rendering part of the application tree until some condition is loaded (fetch data, expensive operation, etc) while displaying some fallback UI such as loading dots. This enables you to show a loading condition per component.

Recently at React Conf 2018, <Suspense /> was released along with lazy(), a new API to aid in code splitting, which when combined with Suspense, enables lazy loading.

I was at the conf when this was announced and couldn't stop thinking about all the places where this could be used to provide a better user experience.

Code splitting with lazy()

Code splitting is a technique used to split your code into several chunks that can be loaded on demand or in parallel, allowing more control on resource load prioritization. So how does it work?

Before:

import HomePage  from "./components/HomePage";
import ArtistPage from "./components/ArtistPage";

With React.lazy():

const HomePage = lazy(() => import("./components/HomePage"));
const ArtistPage = lazy(() => import("./components/ArtistPage"));

What this accomplishes is to create several chunks, one per lazy() loaded component, that will be loaded on demand, when the application needs them.

Coupling it with Suspense

Let's say we have a component that does a big calculation, or something else that makes it take more than a few seconds to load. We could show a loading indicator while this happens as an indication to the user that content is coming. We could keep track of the loading state in a global state with Redux or Context, but Suspense makes it easier:

import React, { Component, Fragment, Suspense, lazy } from "react";

import LoadingDots from "gumdrops/LoadingDots";

const AlbumsSection = lazy(() => import("./AlbumsSection"));

class ArtistPage extends Component {
    render() {
    const artistId = this.props.match.params.id;
        return (
        <Fragment>
        <p>Display Artist Albums</p>
        <Suspense fallback={<LoadingDots />}>
                    <AlbumsSection artistId={artistId} />
                </Suspense>
        </Fragment>
        );
    }
}

And it looks like this; you can see the loading dots being rendered while the component loads.

Note: Suspense currently doesn't work with server side rendering. To lazy load components with SSR, you can use react-loadable.

Concurrent mode

Previously called AsyncMode and currently in 16.7-alpha version, <ConcurrentMode /> is an update to the framework that allows it to concurrently render, suspend and resume render operations, which helps apps adapt to varying user devices and networks. A small example of this (along with Suspense) is that if the connection speed is fast enough, the loading component will not be displayed. By wrapping it in <ConcurrentMode />, users will have a better experience since the page doesn't jump:

Standard mode:

Concurrent mode:

There are two ways to activate Concurrent mode (remember to be in the 16.7-alpha version):

import React, { ConcurrentMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(
    <ConcurrentMode>
        <App />
    </ConcurrentMode>,
    document.getElementById("root"),
);

or

import React, { ConcurrentMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

 

Future

A React Suspense cache implementation is being worked on, that works in conjunction with Suspense to display the fallback JSX while a fetching operation is being done. Currently this is highly experimental, so don't use it in production apps, but it can be tested using the react-cache package. It should return a promise that on resolve resumes the Suspense children component rendering:

import React, { Fragment, Suspense } "react";
import { unstable_createResource } from "react-cache";

const userResource = unstable_createResource(async id => {
    const response = await fetch(`https://your-api-url.com/users/${id}`);
    return await response.json();
});

const UserPage = () => (
	<Fragment>
		<p>Display User Profile</p>
		<Suspense fallback={<p>Loading...</p>}>
			<UserProfile id={1} />
		</Suspense>
	</Fragment>
);

class UserProfile extends React.Component {
    render() {
        const user = userResource.read(this.props.id);
        
        return (
        	<p>The user name is: {user.name}</p>
        );
    }
}

This would display a loading message while the fetch operation is completed, and once the promise resolves, render the component.

To demonstrate the concepts described here, you can explore a repo I made: https://github.com/osiux/gg-suspense-demo/tree/concurrent

At GumGum we are in the process of updating our apps to take advantage of lazy loading with Suspense and provide a better and faster experience to our users.

Links:

Guides