Taming Asynchronous UIs with React “Suspense”

Matt Schultz
Insightful Software
3 min readNov 8, 2018

--

In a simpler time, when a website was requested, the server fetched some data and rendered the entire HTML document. If it took a long time to fetch the data, the visitor simply had to wait before seeing anything. As AJAX became more easily accessible with the likes of Prototype and jQuery, fetching data could be deferred until after an initial document was loaded into the browser. Parts of the UI could be delivered very quickly, then the remainder could be loaded in as it became available. With the advent of modern JavaScript frameworks, this pattern is now second nature.

The Problem

While it’s great that a fast initial page load is now common practice, unfortunately, so is an overabundance of loading indicators. On fast connections, those loading indicators can appear and disappear so quickly that they create a flickering effect in our UI. This isn’t what designers typically have in mind when crafting a user experience, and fine-tuning this behavior can be very complicated. Fortunately, the React team has recognized this problem and is introducing a new API to help us tackle that complexity.

Suspense: Deferred Rendering

This new API, called Suspense, aims to simplify how we build asynchronous UIs. It gives us the ability to defer the rendering of a component until its data has finished loading, as well as control the behavior of our loading indicators. Before we get started, keep in mind that while Suspense has been merged into master as of May, it is still under active development and will likely change before its official release in React 17. In other words, don’t use this in production.

Using Suspense

First, import the createCache and createResource functions from the simple-cache-provider package.

import { createCache, createResource } from ‘simple-cache-provider’;

To create a cache, simply call the createCache function:

let cache = createCache();

Next, define a resource using createResource, which accepts a function that returns a promise:

let PostResource = createResource((id) => {
return fetch(`http://example.com/posts/${id}`).then(response => {
return response.json();
});
});

Now that we have our cache and resource, we can create a component that utilizes Suspense:

let Post = ({ postId }) => {
let post = PostResource.read(cache, postId);
return <h1>{post.title}</h1>;
}

And use it like so:

<React.Placeholder
delayMs={50}
fallback={<div class="spinner"></div>}
>
<Post postId={1} />
</React.Placeholder>

While the API is fairly simple, there’s a bit to digest here, so let’s break it down. When we render our Post component, PostResource.read is called and our cache checks to see if it has data for the provided postId. The first time it checks, however, the cache will be empty, so it throws our promise. React then suspends rendering until that promise is resolved. It seems a little weird, but this is done by utilizing a concept called error boundaries, which was first introduced in React 16.

Placeholders Made Easy

You’ll notice we also wrapped our Post component with a React.Placeholder component:

<React.Placeholder 
delayMs={50}
fallback={<div class="spinner"></div>}
>
<Post postId={1} />
</React.Placeholder>

This placeholder component is what ties this all together and makes Suspense so awesome. While React is waiting for our data to be fetched, it renders the fallback specified in our placeholder, which is typically some sort of loading indicator. After the fetch has been completed, it removes the fallback and resumes rendering our Post component. We can even put multiple Suspense-enabled components inside a single placeholder, effectively grouping multiple data fetches in a single loading indicator.

Setting a Threshold

Last, but not least, you may have noticed the delayMs prop on our placeholder. This specifies the threshold at which our fallback is displayed. In this example, the placeholder will wait 50ms before displaying a loading indicator. This is important because it allows us to forgo that brief, but unnecessary, flicker of our loading indicator when our fetch is effectively instantaneous, but still show one if it takes longer.

Final Thoughts

While Suspense isn’t quite ready for production, it’s certainly worth getting excited about. It gives us a simple, yet powerful, API for creating asynchronous components that scale to our users’ network and computer speeds, allowing us to provide the optimal user experience with minimal code.

To try it out yourself, check out Jared Palmer’s react-suspense-starter project on Github.

--

--