Building a custom React image upload widget with progress bar.
Tags: react
Published: 2021-01-13 05:48:19

In this article, I'll be walking you through how to create a simple custom react file upload widget with a progress upload bar.

## Prerequisites
This article assumes some previous knowledge of basic React and making HTTP requests.

For the purpose of this article, I have created a very basic API to handle the file upload on the server using Nest.js and deployed it to Heroku. Here's the endpoint where we'll be making our POST request to - **https://nestjs-upload.herokuapp.com/** Uploaded files are automatically deleted every 5mins and I have implemented rate-limiting so the upload service can survive public usage for testing.

## Setup a react project using 
```javascript
yarn create react-app upload-widget // NPM works too
```

Feel free to clean up the setup however you like and remove whatever files you don't need.

## The API call

Install Axios by running 
```javascript
yarn add axios
```
Proceed to create a file called upload.js can add the following code to it.

```javascript
import axios from "axios";

const uploadFile = async (body, setPercentage) => {
  try {
    const options = {
      onUploadProgress: (progressEvent) => {
        const { loaded, total } = progressEvent;
        const percent = Math.floor((loaded * 100) / total);

        if (percent <= 100) {
          setPercentage(percent);
        }
      }
    };

    const { data } = await axios.post(
      "https://nestjs-upload.herokuapp.com/",
      body,
      options
    );

    return data;
  } catch (error) {
    console.log(error.message);
  }
};

export default uploadFile;

```

## What's going on?
For the most part, we are simply making a POST request to an endpoint that's supposed to process our request and send back some response... The part that might seem unfamiliar is the
```javascript
onUploadProgress: (progressEvent) => {
  // Do whatever you want with the native progress event
}
```

**onUploadProgress** allows handling of progress events for uploads. There is a **onDownloadProgress** which I believe does the same thing but for downloads.

```javascript
const { loaded, total } = progressEvent;
```

We are destructuring **loaded** and **total** from the **progressEvent** parameter that we are given access to, where **loaded** refers to how much has been uploaded and **total** is the total size to be uploaded

```javascript
const percent = Math.floor((loaded * 100) / total);

if (percent <= 100) {
  setPercentage(percent);
}
```

And finally, we are using some basic maths to calculate the percent and calling a **setPercentage** function which will be passed in as an argument from wherever we call our upload function. 

That wraps up the first part of the task - *writing the upload logic*. It's time to implement the React component that will use this upload function. 

I'll just dump the code then proceed to explain what's happening. 

```javascript
import React, { useState } from "react";
import uploadFile from "./upload";

function UploadWidget() {
  const [percentage, setPercentage] = useState(0);

  const handleFile = async (e) => {
    try {
      const file = e.target.files[0];
      const formData = new FormData();
      formData.append("image", file);

      await uploadFile(formData, setPercentage);
      setPercentage(0);
    } catch (err) {
      setPercentage(0);
      console.log(err);
    }
  };

  return (
    <div>
      <p>Click here to browse</p>
      <input onChange={handleFile} type="file" />
      <p>{percentage} %</p>
    </div>
  );
}

export default UploadWidget;

```

## Breakdown

We've initialized a piece of state to keep track of the upload percentage which would be updated when the **onUploadProgress** event is fired because we passed the **setPercentage** function into our upload function. Below is a code sandbox of a working implementation with the CSS styling included.

I hope you found this helpful. Share your thoughts in the comments section.

{% codesandbox react-upload-widget-swdxi %}

## PS
The goal was to keep this short and focused on the **onUploadProgress** event and how to track the upload progress, you can build up on this by adding file type and size checks and restructuring the code into a nice little reusable **useUpload** hook for your project(s).