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.
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.
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.
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.
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:
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.
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.
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.