React how to display a loading indicator on fetch calls

Intro

Updated to react-promise-tracker v 2.0

When you are developing your web application, you have to perform asynchronous operations, e.g. perform a fetch/ajax call to obtain data from the server. Sometimes you need to do silent background operations, whereas in other cases you need to block the user interface or notify them that something is going on.

A typical way of handling this, is using a boolean flag to show/hide the spinner, something like:

onClick = () => {
  /*
    Begin by setting loading = true, and use the callback function
    of setState() to make the ajax request. Set loading = false after
    the request completes.
  */
  this.setState({ loading: true }, () => {
    Axios.get('/endpoint')
      .then(result => this.setState({
        loading: false,
        data: [...result.data],
      }));
  });
}
render() {
      const { data, loading } = this.state;

      return (
        <div>
          <button onClick={this.onClick}>
            Load Data
          </button>

          {/*
            Check the status of the 'loading' variable. If true, then display
            the loading spinner. Otherwise, display the results.
          */}
          {loading ? <LoadingSpinner /> : <ResultsTable results={data} />}
        </div>
      );
}

What's wrong with this approach? Let's list the pitfalls:

  • What happens if you forget to set the loading indicator to false? You will receive a call from your clients indicating that the site is not responding (just because of a missing set to false condition, gasp!!).
  • What happens if you have several ajax requests being launched in parallel? You need to add a counter and keep track of all of them till they have finished.
  • What happens if any of the ajax calls failed? You have to make sure you put the loading = false statement in the right place.
  • What if in some cases you want to track the promise, but in other cases you don't (silent execution in the background)?
  • And if you need to display more than one loading indicator? (e.g. you want to display the data loading of a given area of the screen).

You can try to handle all of these scenarios, but you will probably end up with race conditions, plus a mix of UI and business logic code.

What can we do? You can leverage this complexity to a microlibrary like React Promise Tracker, which will take care of all those edge cases, making it easier for you to manage it all. Features of this library:

  • Tracks any kind of async process based on promises (including async/await calls).
  • Includes an HOC that can feed a loading indicator component with the loading true/false flag.
  • Can be used from any part of the code (component, plain javascript, redux...).
  • Keeps track of parallel asynchronous calls.
  • Keeps track of errors.
  • Allows you to define multiple areas.

Let's add this library to an existing project.

The example

As a starting point, we will take a simple application that displays a list of users. We are hitting the json server api ... (we have intentionally added a delay, just to create the opportunity of showing you the loading indicator). Right now we have the following user experience:

Async call no loading indicator

We want to display a loading indicator whenever the user clicks on the Load button, and hide it once the corresponding ajax call has been completed. Something like this:

Async call good looking indicator

Let's get started

npm install
  • Let's install the library react promise tracker:
npm install react-promise-tracker --save
  • Let's create a very basic loading indicator, let's call it InnerLoadingIndicator.

./src/index.js

import React, { Component } from 'react';
import { render } from 'react-dom';
import { App } from './app';

 
+ const LoadingIndicator = props => {
+   return (
+    <h1>Hey some async call in progress ! </h1>
+  );  
+ }

render(
  <div>
    <App />
  </div>,
  document.getElementById('root'));
  • Now we need a way to know whether the loading indicator needs to be shown or not. React promise tracker implements a custom hook that can report the current status to our loading indicator component.

Let's start by importing the promiseTrackerHoc

./src/index.js

import React from 'react';
+ import { usePromiseTracker } from "react-promise-tracker";

Use the react-promise-tracker usePromiseTracker hook.

./src/index

 const LoadingIndicator = props => {
+   const { promiseInProgress } = usePromiseTracker();

   return (
+     promiseInProgress && 
    <h1>Hey some async call in progress ! </h1>
  );  
 }
  • We can now instantiate this component at our application entry point level:

./src/index.ts

render(
  <div>
    <App />
+   <LoadingIndicator/>
  </div>,
  document.getElementById('root'));
  • Now let's jump to the place where we are making a fetch call. We will wrap the fetch call with a trackPromise method.

First we will add the import to the react promise tracker library:

./app.js

import { UserTable, LoadButton } from './components';
import './app.css';
+ import { trackPromise } from 'react-promise-tracker';

Then we will wrap the fetch call with a trackPromise method:

app.js

  onLoadTables() {
    this.setState({
      users: [],
    });

+   trackPromise(
    userAPI.fetchUsers()
      .then((users) => {
        this.setState({
          users,
        })
-      });
+      }));
  }
  • If we run the project now, we can see that the loading indicator is being shown when the asynchronous call is in progress. If we want to track any async call that returns a promise, we only have to wrap it with the trackPromise method, react-promise-tracker.

Async call ugly loading indicator

  • To wrap up this example, let's make a more professional-looking loadingIndicator. We will install a library called react loader spinner.
npm install react-loader-spinner --save
  • Let's define the spinner. For the sake of simplicity we will define the styles inline.

First let's add the corresponding import:

./src/index.js

import React, { Component } from 'react';
import { render } from 'react-dom';
import { App } from './app';
+ import Loader from 'react-loader-spinner';
  • Now let's pimp our loading indicator:

    • We will center the spinner to be displayed.
    • We will add one of the spinners that read-loader-spinner offers.

./src/index.js

const LoadingIndicator = props => {
  const { promiseInProgress } = usePromiseTracker();

  return promiseInProgress && 
-        <h1>Hey some async call in progress ! </h1>;
+    <div
+      style={{
+        width: "100%",
+        height: "100",
+        display: "flex",
+        justifyContent: "center",
+        alignItems: "center"
+      }}
+    >
+      <Loader type="ThreeDots" color="#2BAD60" height="100" width="100" />
+    </div>
};

-If we run the application now, we can see that we are getting a better-looking loading indicator.

npm start

Async call good looking indicator

Wrapping up

Handling async loading indicators by yourself can be okay for very small applications. However, for the rest of the scenarios it's a good idea to leave the task to a microlibrary, like React Promise Tracker.

If you want to learn more about React-Promise-Tracker, you can check out its github page. It contains live examples plus documentation.

https://github.com/Lemoncode/react-promise-tracker

About Basefactor

We are a team of Javascript experts. If you need coaching or consultancy services, don't hesitate to contact us.

Doers/

Location/

C/ Pintor Martínez Cubells 5 Málaga (Spain)

General enquiries/

info@lemoncode.net

+34 693 84 24 54

Copyright 2018 Basefactor. All Rights Reserved.