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