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:
- Allow users to select a file from File Explorer.
- Display file name and file size on the screen.
- Display image on the screen for PNG file formats.
- Upload image to a server through a POST request.
- Show upload progress and status on the screen.
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:
- action: The server URL where the file will be uploaded through a POST request.
- accept: File formats that should be allowed by file explorer.
- beforeUpload(): JavaScript function which executes just before proceeding with upload operation.
- onSuccess(): JavaScript callback function is called after the upload is completed.
- onProgress(): JavaScript function which is executed everytime upload progress increases.
- 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;
}