Create File Upload component in React JS

In this article, we will learn how to create a file upload React JS component which sends POST requests to a server. The React JS app will have the following features:

  1. Allow users to select a file from File Explorer.
  2. Display file name and file size on the screen.
  3. Display image on the screen for PNG file formats.
  4. Upload image to a server through a POST request.
  5. Show upload progress and status on the screen.
react-file-upload
Screenshot of Uploading PNG file

1) Install “rc-upload” and “rc-progress” npm packages

The “rc-upload” npm package provides <Upload/> component import which simplifies file upload functionality. In addition, we need “rc-progress” package to display a horizontal progress bar to indicate the upload percentage.

npm i rc-upload
npm i rc-progress

// For projects configured with yarn 
yarn add rc-upload
yarn add rc-progress

2) React States for the file name, size and image data

The file details such as name, size, and image data are dynamic values that vary based on the file selected by the user, hence they are managed with React state.

const [imgData, setImgdata] = useState();
const [fileName, setFileName] = useState();
const [fileSize, setFileSize] = useState();

3) Display Upload Progress

The file upload operation is associated with time delay, hence displaying the upload percentage will improve the user experience. The upload percentage is a dynamic value, representing it as a React state.

const [percentage, setPercentage] = useState(0);
const [isUploading, setIsUploading] = useState(false);

Additional loading state is required to conditionally render the upload percentage or “Finished” text.

<div className="progress-text">
  {isUploading ? `Uploading ${percentage}% ` : `Finished`}
</div>

4) Allow file upload with POST API call

The <Upload> components are managed by configuring props values, which are as follows:

  1. action: The server URL where the file will be uploaded through a POST request.
  2. accept: File formats that should be allowed by file explorer.
  3. beforeUpload(): JavaScript function which executes just before proceeding with upload operation.
  4. onSuccess(): JavaScript callback function is called after the upload is completed.
  5. onProgress(): JavaScript function which is executed everytime upload progress increases.
  6. onError(): JavaScript callback function when error occurs during file upload.

In beforeUpload() callback function, we update file details and set uploading as true. Additionally, the code for base64 image format is generated If the file is in .png format.

In onProgress(), the upload percentage is updated every time there is progress and the error object is logged if an error occurs during the file upload.

import Upload from "rc-upload";

const props = {
  action: "https://httpbin.org/post",
  accept: ".png, .pdf, .txt",
  beforeUpload(file) {
    // Start upload
    setIsUploading(true);
    // Set file details
    setFileName(file.name);
    setFileSize(Math.floor(file.size / 1000));
    // Display image for .png format
    if (file.type === "image/png") {
      const reader = new FileReader();
      reader.onloadend = () => {
        setImgdata(reader.result);
      };
      reader.readAsDataURL(file);
    }
  },
  onSuccess() {
    // Finish upload
    setIsUploading(false);
  },
  onProgress(step) {
    // Update progress
    setPercentage(Math.round(step.percent));
  },
  onError(err) {
    console.log("onError", err);
  }
};
<Upload {...props}>
  <button id="upload-button">Upload File</button>
</Upload>

5) Display the uploaded image on the screen

The base 64 code for the selected png file is assigned to the imgData state. Hence, the image is displayed through <img/> tag with imgData state as the source.

{imgData && (
  <div>
    <img src={imgData} alt="uploaded" width="250" />
  </div>
)}

6) Create Progress bar for upload

The progress bar is displayed using <Line/> component available from “rc-progress” NPM package. The upload percentage is passed as a prop to reflect the upload completion on screen.

import { Line } from "rc-progress";
<div className="progress-container">
  <Line
    percent={percentage}
    strokeWidth={9}
    trailWidth={9}
    trailColor="#FFF"
    strokeColor={isUploading ? "#41C3D2" : "#92ed14"}
  />
  <div className="progress-text">
    {isUploading ? `Uploading ${percentage}% ` : `Finished`}
  </div>
</div>

Final Solution Code

App.js

import React, { useState } from "react";
import "./styles.css";
import { Line } from "rc-progress";
import Upload from "rc-upload";

export default function App() {
  const [percentage, setPercentage] = useState(0);
  const [imgData, setImgdata] = useState();
  const [isUploading, setIsUploading] = useState(false);
  const [fileName, setFileName] = useState();
  const [fileSize, setFileSize] = useState();

  const props = {
    action: "https://httpbin.org/post",
    accept: ".png, .pdf, .txt",
    beforeUpload(file) {
      // Start upload
      setIsUploading(true);
      // Set file details
      setFileName(file.name);
      setFileSize(Math.floor(file.size / 1000));
      // Display image for .png format
      if (file.type === "image/png") {
        const reader = new FileReader();
        reader.onloadend = () => {
          setImgdata(reader.result);
        };
        reader.readAsDataURL(file);
      }
    },
    onSuccess() {
      // Finish upload
      setIsUploading(false);
    },
    onProgress(step) {
      // Update progress
      setPercentage(Math.round(step.percent));
    },
    onError(err) {
      console.log("onError", err);
    }
  };

  return (
    <div className="App">
      {fileName && (
        <React.Fragment>
          {imgData && (
            <div>
              <img src={imgData} alt="uploaded" width="250" />
            </div>
          )}
          <div className="upload-list">
            <div className="file-name">
              <b>{fileName}</b>
            </div>
            <div className="progress-container">
              <Line
                percent={percentage}
                strokeWidth={9}
                trailWidth={9}
                trailColor="#FFF"
                strokeColor={isUploading ? "#41C3D2" : "#92ed14"}
              />
              <div className="progress-text">
                {isUploading ? `Uploading ${percentage}% ` : `Finished`}
              </div>
            </div>
            <div className="file-size">{`${fileSize} KB`}</div>
          </div>
        </React.Fragment>
      )}
      <Upload {...props}>
        <button id="upload-button">Upload File</button>
      </Upload>
    </div>
  );
}

styles.css

.App {
  font-family: sans-serif;
  text-align: center;
  height: 80vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 20px;
}
.progress-container {
  width: 250px;
  color: #363a43;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  height: 19px;
  font-weight: normal;
  font-size: 14px;
  color: #363a43;
}

.file-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 200px;
}
.upload-list {
  display: flex;
  gap: 10px;
  align-items: center;
  background: #f0f0f0;
  border: 0.1rem solid #cfdbde;
  border-radius: 0.2rem;
  padding: 10px;
  width: 550px;
}
.file-size {
  color: #7eb631;
}

#upload-button {
  background-color: indigo;
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  font-family: sans-serif;
  border-radius: 0.3rem;
  cursor: pointer;
  font-size: 16px;
}
#upload-button:hover {
  opacity: 0.8;
}