How to build a live progress bar with React

Introduction

Long running tasks are a feature of many web applications which need to be handled properly to improve the experience of the user. In many cases, a static progress bar that doesn’t update until the end is provided which leaves the user unsure of how long a task will take or if progress is being made at all.

We can improve this situation by making our progress bars show the actual progress of the task in realtime, and Pusher Channels makes this really easy to do as you’ll see in the tutorial below. To get started with Pusher Channels, create a free sandbox Pusher account or sign in.

Prerequisites

Previous experience with React is required to follow through with this tutorial. You also need to install Node.js (v6 and above) and npm on your machine if you don’t have them already. Installation instructions can be found here.

Getting started

Open a new terminal window and create a new folder called progress-bar, then cd into it:

1mkdir progress-bar
2    cd progress-bar

Next, install create-react-app, a tool that allows us to quickly get a React application up and running:

    npm install -g create-react-app

Once create-react-app is installed, use it to bootstrap a new React project. Run the command below within the progress-bar directory.

    create-react-app client

The above command will create a new directory called client and install React as well as its accompanying dependencies. It may take a while to complete, so sit tight and wait. Once it’s done, you should see a some information in the terminal informing you of what you can do next.

Next, change into the newly created directory (cd client)and run yarn start to start the development server. Once the application compiles, you will be able to view it at http://localhost:3000.

Build the application frontend

For this demo, we will simulate the common task of uploading a large file to the server. We’re not going to upload a real file; however, we’ll write a small Node script that will simulate the effect of a file upload.

Let’s start by building the application frontend first. We need some additional dependencies for our React application, so let’s install them first. Within the client directory, run the following command:

    npm install pusher-js react-ladda

pusher-js is the client side SDK for Channels, while react-ladda lets us use the Ladda button library in our React app.

Open up App.js in your favorite text editor and change its contents to look like this:

1// client/src/App.js
2    
3    import React, { Component } from 'react';
4    import LaddaButton, { XL, EXPAND_RIGHT } from 'react-ladda';
5    import Pusher from 'pusher-js';
6    import './App.css';
7    
8    class App extends Component {
9      state = {
10        loading: false,
11        progress: 0,
12      };
13    
14      componentDidMount() {
15        const pusher = new Pusher('<your app key>', {
16          cluster: '<your app cluster>',
17          encrypted: true,
18        });
19    
20        const channel = pusher.subscribe('upload');
21        channel.bind('progress', data => {
22          this.setState({
23            progress: data.percent / 100,
24          });
25    
26          if (data.percent === 100) {
27            this.setState({
28              loading: false,
29              progress: 0,
30            });
31          }
32        });
33      }
34    
35      handleClick = event => {
36        event.preventDefault();
37    
38        this.setState({
39          loading: !this.state.loading,
40        });
41    
42        fetch('http://localhost:5000/upload', {
43          method: 'POST',
44        }).catch(error => console.log(error));
45      };
46    
47      render() {
48        const { loading, progress } = this.state;
49        const message = loading ? (
50          <span className="progress-text">{progress * 100}% completed</span>
51        ) : null;
52    
53        return (
54          <div className="App">
55            <h1>Imaginary Image Upload Service :)</h1>
56            <LaddaButton
57              loading={this.state.loading}
58              onClick={this.handleClick}
59              progress={this.state.progress}
60              data-color="#eee"
61              data-size={XL}
62              data-style={EXPAND_RIGHT}
63              data-spinner-size={30}
64              data-spinner-color="#ddd"
65              data-spinner-lines={12}
66            >
67              Upload really large image!
68            </LaddaButton>
69    
70            {message}
71          </div>
72        );
73      }
74    }
75    
76    export default App;

Our React application consists of one button which, when clicked, will show the progress of the file upload. The componentDidMount() lifecycle method houses the logic for streaming upload progress to the app in realtime.

We’re opening a connection to Channels using the subscribe() method which allows us to subscribe to a new channel called upload. Then, we listen for the progress event on the upload channel using the bind method and update the application state once we receive a progress update.

Before you can integrate Channels into your application you need to sign up for a free account on Pusher. Once your account is created, select Channels apps on the sidebar, and hit Create Channels app to create a new app. Retrieve your credentials from the API Keys tab, and then replace the <your app key> and <your app cluster> placeholders in App.js with the appropriate values.

Add the styles for the application

Let's add the styles for the app’s frontend. Open up App.css in your editor and replace its contents with the following styles:

1// client/src/App.css
2    
3    .App {
4      margin-top: 50px;
5      text-align: center;
6    }
7    
8    .progress-text {
9      display: block;
10      font-size: 16px;
11      margin-top: 20px;
12    }

You also need to add the style for the Ladda button. You can do so by adding the following tag to the <head> of the index.html file within the client/public directory:

    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/Ladda/1.0.0/ladda.min.css">

At this point, the application should look like this:

react-progress-bar-demo-1

Create the server component

We’ll set up the server in the next step so as to simulate the file upload and trigger upload progress updates from the server. Run the following command from the root of the progress-bar directory to install the necessary dependencies:

    npm install express cors dotenv pusher

Next, create a new file called server.js in the root of your project directory and paste in the following code to set up a simple express server:

1// server.js
2    
3    require('dotenv').config({ path: 'variables.env' });
4    
5    const express = require('express');
6    const cors = require('cors');
7    const Pusher = require('pusher');
8    
9    const pusher = new Pusher({
10      appId: process.env.PUSHER_APP_ID,
11      key: process.env.PUSHER_APP_KEY,
12      secret: process.env.PUSHER_APP_SECRET,
13      cluster: process.env.PUSHER_APP_CLUSTER,
14      encrypted: true,
15    });
16    
17    const app = express();
18    
19    app.use(cors());
20    
21    app.set('port', process.env.PORT || 5000);
22    const server = app.listen(app.get('port'), () => {
23      console.log(`Express running → PORT ${server.address().port}`);
24    });

Create another file called variables.env in the root of your project directory and change it’s contents to look like this:

1// variables.env
2    
3    PORT=5000
4    PUSHER_APP_ID=<your app id>
5    PUSHER_APP_KEY=<your app key>
6    PUSHER_APP_SECRET=<your app secret>
7    PUSHER_APP_CLUSTER=<your app cluster>

Remember, your Pusher credentials can be retrieved from the API Keys tab on the Pusher dashboard.

Add the /upload route

If you check the handleClick() method within App.js, you will see that we are making a post request to /upload when the button is clicked. Let’s go ahead and create this route within server.js:

1// server.js
2    
3    ...
4    app.use(cors());
5    
6    app.post('/upload', (req, res) => {
7      let percent = 0;
8      const interval = setInterval(() => {
9        percent += 10;
10        pusher.trigger('upload', 'progress', {
11          percent,
12        });
13    
14        if (percent === 100) clearInterval(interval);
15      }, 2000);
16    });

We’re simulating an upload progress of 10% every two seconds, and triggering a new update on check increment.

You can start the server by running node server.js in a new terminal window and try out the application by clicking the upload button. You should see the progress update in realtime.

react-progress-bar-demo-2

Conclusion

And that’s it! This is just scratching the surface of realtime updates using Pusher. Check out some other use cases for Channels, and as always, you can find the source code of this app in this GitHub repository.