Building a custom React image upload widget with a progress bar.

Building a custom React image upload widget with a progress bar.

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

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

yarn add axios

Proceed to create a file called upload.js can add the following code to it.

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

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.

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

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.

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.

Code Sandbox Link

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