Async Rendering in React with Suspense
What is Suspense?
Suspense is a new React feature that was announced recently at the JSConf Conference in Iceland. It aims to help with handling async operations respectively in regard to CPU power and data fetching.
Suspense allows you to defer rendering part of your application tree until some condition is met (for example, data from an endpoint or a resource is loaded).
In this article, we’ll explore Suspense and see what potential impact this feature will have on the way React apps are built.
Why Suspense?
There’s a good chance you’ve come across SPAs that make use of a loading icon as an indicator that data is being fetched. This is a common method used to ensure good UX for apps that are fetching data from external sources. All you have to do is check if the data has been successfully fetched, and if not, show a spinner.
However, this might not scale when the data fetching process becomes complicated:
- When both the parent and child component have loading states
- When you need a component to load only after some other (child) components have been loaded
The key module that makes Suspense work is the createFetcher function. Available on npm as the simple-cache-provider, it works as outlined below:
- In the render() method, read a value from the cache
- If the value is already cached, the render continues like normal
- If the value is not already cached, the cache throws an error
- When the promise resolves, React continues from where it stopped
import { createResource } from 'simple-cache-provider';
const someFetcher = createResource(async () => {
const res = await fetch(`https://api.github.com/search/users?q=yomete`);
return await res.json();
});
export default someFetcher;
We create a fetcher function using createResource from the simple-cache-provider package.
Note: The simple-cache-provider is still an experimental feature, not to be used in a production app.
When initiating createResource, a function is passed which is expected to return a Promise. If the Promise resolves, React carries on and render the results, else, an error is thrown.
The function can then be used in a render function to display the results.
Let’s look at an example of Suspense in action.
Suspense demo
The codebase for the demo can be accessed on GitHub and the live demo can be accessed here.
We’ll be using the create-react-app package to create a new React project, with some modifications. Run the command below in your terminal to generate a React app:
npx create-react-app react-suspense
This creates a folder titled react-suspense which contains the React app. Now, let’s make the aforementioned modifications. To make use of the experimental features such as simple-cache-provider , the React version in the package.json file needs to be bumped up to the alpha version.
Therefore, your package.json file (the dependencies object) should be updated with the code snippet below:
"react": "16.4.0-alpha.0911da3",
"react-dom": "16.4.0-alpha.0911da3",
The alpha version shown above is the version of React we need to carry out our tests. Run the npm install command to update all dependencies.
Let’s also install the simple-cache-provider package with the terminal command below:
npm install simple-cache-provider
With all the dependencies installed, let’s go ahead and write the code that we’ll use to demo Suspense.
The idea here is to get a list of shows from the TV Maze API and then display the results using Suspense.
To begin, we’d need to do some imports in the App.js file. The first will be to import the createResource function in the App.js file. This will be imported from the simple-cache-provider :
import { createResource } from 'simple-cache-provider';
Next, we’ll import a component, not yet created, titled withCache. This is a Higher Order Component (HOC) that helps with Suspense rendering:
import { withCache } from './components/withCache';
Create a folder, name it components and in it create a .withCache.js file and edit with the code below:
import React from 'react';
import { SimpleCache } from 'simple-cache-provider';
export function withCache(Component) {
return props => (
<SimpleCache.Consumer>
{cache => <Component cache={cache} {...props} />}
</SimpleCache.Consumer>
);
}
The withCache component is a Higher Order Component that connects with SimpleCache.Consumer and puts the cache over the wrapped component.
Next, we’ll navigate back to the App.js and create the createResource function to fetch the data:
const sleep = ms => new Promise(r => setTimeout(() => r(), ms));
const readShows = createResource(async function fetchNews() {
await sleep(3000);
const res = await fetch(`http://api.tvmaze.com/search/shows?q=suits`);
return await res.json();
});
Here’s what the createResource function is doing exactly:
- It creates a resource fetcher (createResource()) which is called with a set of parameters, in this case, an async function that fetches the list of shows titled suits, only after ‘waiting’ for the specified duration in the sleep function
- It returns the result of the API call
It’s important to note that the sleep function is only being used so as simulate a longer API call for this example.
With the createResource function created, we’ll need to get the results from the async function above and then build the view to display the results. In the App.js file, go ahead and add the code block below:
const Movies = withCache( (props) => {
return (
<React.Fragment>
<div className="column is-4">
<div className="movie">
<div className="movie__left">
<img src />
</div>
<div className="movie__right">
<div className="movie__right__title">Name: </div>
<div className="movie__right__subtitle">Score: </div>
<div className="movie__right__subtitle">Status: </div>
<div className="movie__right__subtitle">Network: </div>
<a href target="_blank" className="movie__right__subtitle">Link</a>
</div>
</div>
</div>
</React.Fragment>
)
});
In the code above, a stateless component is created and wrapped in the withCache Higher Order Component. It returns the necessary HTML to build the view that is needed to display the results from the API.
Also, the Bulma CSS framework is being used to help with styling. That can be added to the project by adding the line of code below to the index.html :
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
The next course of action is to actually read the data from the createResource() function and then spit it out into the Movies component.
In the Movie.js component, just before the return function, add the line of code below:
const result = readShows(props.cache);
Here we are using readShows(props_.cache_) which either resolves the promise value or throws an error. Since readShows is the createResource function, it expects a parameter of cache which is props.cache in this case. The cache is being passed from the withCache HOC as a prop.
The result of the API call is then stored in the result variable. With the API result being fetched, we can now use that to populate the view:
const Movies = withCache( (props) => {
const result = readShows(props.cache);
return (
<React.Fragment>
{result &&
result.length &&
result.map(item => (
<div className="column is-4">
<div className="movie">
<div className="movie__left">
<img src={item.show.image.original} />
</div>
<div className="movie__right">
<div className="movie__right__title">{item.show.name}</div>
<div className="movie__right__subtitle">Score: {item.show.rating.average}</div>
<div className="movie__right__subtitle">Status: {item.show.status}</div>
<div className="movie__right__subtitle">Network: {item.show.network ? item.show.network.name : 'N/A'}</div>
<a href={item.show.url} target="_blank" className="movie__right__subtitle">Link</a>
</div>
</div>
</div>
))
}
</React.Fragment>
)
});
Remember we mentioned above, that Suspense helps with async rendering by deferring rendering part of your application tree until some data or resource has been fetched. This is very important as it can be used to display some loading message as a feedback to users who are waiting for data on the screen.
Let’s go ahead and implement this in our app:
const Placeholder = ({ delayMs, fallback, children }) => {
return (
<Timeout ms={delayMs}>
{didExpire => {
return didExpire ? fallback : children;
}}
</Timeout>
);
}
The component above accepts the following:
- ms prop, which indicates the time after which we want to see the fallback content. This is passed to the Placeholder component as delayMS
- fallback is the loading state that is shown when data is being fetched
- children which should be a “function as a child” or “render prop” function; this function will be called with one parameter, which indicates if the specified time elapsed
We use the Placeholder component to capture the throw by the fetcher and know the state of the data that’s being loaded.
Pulling all of this together, you can go ahead to edit the App component with the code block below:
export default class App extends React.Component {
render() {
return (
<React.Fragment>
<div className="App">
<header className="App-header">
<h1 className="App-title">React Suspense Demo</h1>
</header>
<div className="container">
<div className="columns is-multiline">
<Placeholder delayMs={1000} fallback={<div>Loading</div>}>
<Movies />
</Placeholder>
</div>
</div>
</div>
</React.Fragment>
);
}
}
As seen above, the Placeholder component is the parent component to the Movies component. The fallback props on the Placeholder component is sent to a nice and simple loading text.
There you have it, you can go ahead to start the app with the npm start command and you should see Suspense in action.
Conclusion
With Suspense, you have the ability to suspend component rendering while async data is being loaded. You can pause any state update until the data is ready, and you can add async loading to any component deep in the tree without plumbing all the props and state through your app and hoisting the logic.
This results in an instantaneous and fluid UI for fast networks and an intentionally designed loading state for slow networks as opposed to a general loading state.
It’s important to note that these APIs are still in experimental mode and not suitable for production. It’s best to always stay in tune with the React team for any API changes and updates to the Suspense feature.
The codebase for the demo above can be accessed on GitHub and the live demo can be accessed here.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.
The post Async Rendering in React with Suspense appeared first on LogRocket Blog.
Top comments (0)