React’s Experimental Suspense API Will Rock for Fallback UI During Data Fetches

Publikováno: 2.2.2019

Most web applications built today receive data from an API. When fetching that data, we have to take certain situations into consideration where the data might not have been received. Perhaps it was a lost connection. Maybe it was the endpoint was changed. Who knows. Whatever the issue, it's the end user who winds up with a big bag of nothing on the front end.

So we ought to account for that!

The common way of handling this is … Read article

The post React’s Experimental Suspense API Will Rock for Fallback UI During Data Fetches appeared first on CSS-Tricks.

Celý článek

Most web applications built today receive data from an API. When fetching that data, we have to take certain situations into consideration where the data might not have been received. Perhaps it was a lost connection. Maybe it was the endpoint was changed. Who knows. Whatever the issue, it's the end user who winds up with a big bag of nothing on the front end.

So we ought to account for that!

The common way of handling this is to have something like an isLoading state in the app. The value of isLoading is dependent on the data we want to receive. For example, it could be a simple boolean where a returned true (meaning we're still waiting on the data), we display a loading spinner to indicate that the app is churning. Otherwise, wee'll show the data.

Oh god, no!
📷 Credit: Jian Wei

While this isn‘t entirely bad, the awesome folks working on React have implemented (and are continuing to work on) a baked-in solution to handle this using a feature called Suspense.

Suspense sorta does what its name implies

You may have guessed it from the name, but Suspense tells a component to hold off from rendering until a condition has been met. Just like we discussed with isLoading, the rendering of the data is postponed until the API fetches the data and isLoading is set to false. Think of it like a component is standing in an elevator waiting for the right floor before stepping out.

At the moment, Suspense can only be used to conditionally load components that use React.lazy() to render dynamically, without a page reload. So, say we have a map that takes a bit of time to load when the user selects a location. We can wrap that map component with Suspense and call something like the Apple beachball of death to display while we're waiting on the map. then, once the map loads, we kick the ball away.

// Import the Map component
const Map = React.lazy(() => import('./Map'));

function AwesomeComponent() [
  return (
    // Show the <Beachball> component until the <Map> is ready
    <React.Suspense fallback={<Beachball />}>
      <div>
        <Map />
      </div>
    </React.Suspense>
  );
}

Right on. Pretty straightforward so far, I hope.

But what if we want the fallback beachball, not for a component that has loaded, but when waiting for data to be returned from an API. Well, that's a situation Suspense seems perfectly suited for, but unfortunately, does not handle that quite yet. But it will.

In the meantime, we can put an experimental feature called react-cache (the package previously known as simple-cache-provider) to demonstrate how Suspense ought to work with API fetching down the road.

Let's use Suspense with API data anyway

OK, enough suspense (sorry, couldn‘t resist). Let's get to a working example where we define and display a component as a fallback while we're waiting for an API to spit data back at us.

Remember, react-cache is experimental. When I say experimental, I mean just that. Even the package description urges us to refrain from using it in production.

Here's what we're going to build: a list of users fetched from an API.

Get Source Code

Alright, let's begin!

First, spin up a new project

Let's start by generating a new React application using create-react-app.

## Could be any project name
create-react-app csstricks-react-suspense

This will bootstrap your React application. Because the Suspense API is still a work in progress, we will make use of a different React version. Open the package.json file in the project's root directory, edit the React and React-DOM version numbers, and add the simple-cache-provider package (we'll look into that later). Here's what that looks like:

"dependencies": {
  "react": "16.4.0-alpha.0911da3",
  "react-dom": "16.4.0-alpha.0911da3",
  "simple-cache-provider": "0.3.0-alpha.0911da3"
}

Install the packages by running yarn install.

In this tutorial, we will build the functionality to fetch data from an API. We can use the createResource() function from simple-cache-provider to do that in the src/fetcher.js file:

import { createResource } from 'simple-cache-provider';

const sleep = (duration) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, duration)
  })
}

const loadProfiles = createResource(async () => {
  sleep(3000)
  const res = await fetch(`https://randomuser.me/api/?results=15`);
  return await res.json();
});

export default loadProfiles

So, here's what's happening there. The sleep() function blocks the execution context for a specific duration, which will be passed as an argument. The sleep() function is then called in the loadProfiles() function to stimulate a delay of three seconds (3,000ms). By using createResource() to make the API call, we either return the resolved value (which is the data we are expecting from the API) or throw a promise.

Next, we will create a higher-order component called withCache that enable caching on the component it wraps. We'll do that in a new file called, creatively, withCache.js. Go ahead and place that in the project's src directory.

import React from 'react';
import { SimpleCache } from 'simple-cache-provider';

const withCache = (Component) => {
  return props => (
    <SimpleCache.Consumer>
      {cache => <Component cache={cache} {...props} />}
    </SimpleCache.Consumer>
  );
}

export default withCache;

This higher-order component uses SimpleCache from the simple-cache-provider package to enable the caching of a wrapped component. We'll make use of this when we create our next component, I promise. In the meantime, create another new file in src called Profile.js — this is where we'll map through the results we get from the API.

import React, { Fragment } from 'react';
import loadProfiles from './fetcher'
import withCache from './withCache'

// Just a little styling
const cardWidth = {
  width: '20rem'
}

const Profile = withCache((props) => {
  const data = loadProfiles(props.cache);
  return (
    <Fragment>
      {
        data.results.map(item => (
        <div key={item.login.uuid} className="card" style={cardWidth}>
          <div>
            <img src={item.picture.thumbnail} />
          </div>
            <p>{item.email}</p>
          </div>
        ))
      }
    </Fragment>
  )
});

export default Profile

What we have here is a Profile component that's wrapped in withCache the higher-order component we created earlier. Now, whatever we get back from the API (which is the resolved promise) is saved as a value to the data variable, which we've defined as the props for the profile data that will be passed to the components with cache (props.cache).

To handle the loading state of the app before the data is returned from the API, we'll implement a placeholder component which will render before the API responds with the data we want.

Here's what we want the placeholder to do: render a fallback UI (which can be a loading spinner, beach ball or what have you) before the API responds, and when the API responds, show the data. We also want to implement a delay (delayMs ) which will come in handy for scenarios where there's almost no need to show the loading spinner. For example; if the data comes back in less than two seconds, then maybe a loader is a bit silly.

The placeholder component will look like this;

const Placeholder = ({ delayMs, fallback, children }) => {
  return (
    <Timeout ms={delayMs}>
      {didTimeout => {
        return didTimeout ? fallback : children;
      }}
    </Timeout>
  );
}

delayMs, fallback and children will be passed to the Placeholder component from the App component which we will see shortly. The Timeout component returns a boolean value which we can use to either return the fallback UI or the children of the Placeholder component (the Profile component in this case).

Here's the final markup of our App, piecing together all of the components we've covered, plus some decorative markup from Bootstrap to create a full page layout.

class App extends React.Component {
  render() {
    return (
      <React.Fragment>
        // Bootstrap Containers and Jumbotron     
        <div className="App container-fluid">
          <div className="jumbotron">
            <h1>CSS-Tricks React Suspense</h1>
          </div>
          <div className="container">
            <div>
              // Placeholder contains Suspense and wraps what needs the fallback UI
              <Placeholder
                delayMs={1000}
                fallback={
                  <div className="row">
                    <div className="col-md">
                      <div className="div__loading">
                        <Loader />
                      </div>
                    </div>
                  </div>
                }
              >
                <div className="row">
                  // This is what will render once the data loads
                  <Profile />
                </div>
              </Placeholder>
            </div>
          </div>
        </div>
      </React.Fragment>
    );
  }
}

That's a wrap

Pretty neat, right? It's great that we're in the process of getting true fallback UI support right out of the React box, without crafty tricks or extra libraries. Totally makes sense given that React is designed to manage states and loading being a common state to handle.

Remember, as awesome as Suspense is (and it is really awesome), it is important to note that it's still in experimental phase, making it impractical in a production application. But, since there are ways to put it to use today, we can still play around with it in a development environment all we want, so experiment away!

Folks who have been working on and with Suspense have been writing up their thoughts and experience. Here are a few worth checking out:

The post React’s Experimental Suspense API Will Rock for Fallback UI During Data Fetches appeared first on CSS-Tricks.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace