In this blog, I will explain about, How to upload multiple images with a progress bar.
To upload the multiple images, Here I used,
- Fetch requests for Image upload,
- Bootstrap for UI,
- Sockets for a progress bar.
Technology
- React-16
- Bootstrap-4
- Axios-0.19.2
Web Api for File upload and storage
Methods | Url | Action |
---|---|---|
POST | uploads | To upload an image |
GET | /path/filename | To display an image |
Download | /path/filename | To download an image |
React Code
import React,{component} from “react”;
import “bootstrap/dist/css/bootstrap.min.css”;
import CircularProgress from “@material-ui/core/CircularProgress;
class App extends Component{
state={
filesArray:[],
progreeData:{},
}
render(){
return(
< div className=”onDragOver” >
< div className=”dropZone__imgs-wrapper” >
{ this.state.filesArray && this.state.filesArray.length
&& this.state.filesArray.length >0
?this.state.filesArray.map((file,i)=>
file.type&&file.type.startsWith(“image”)?(
< div className=”d-flex” styl{{width:”19%”}}>
< div className=”dropZone__img”
key={i}
style={{backgroundImage:’url(${file.preview})’}}
>
< Icon
className=”dropZone__img”
color=”error”
onClick={()=>this.removeFile(i,file)}>
> delete
< /Icon >
< / div >
< CircularProgress
className=”centered”
style={{width:”25px”,height:”25px”}}
variant=”static”
value={file && file.percentage ? file.percentage:0} / >
< / div>
):(
< div className=”dropZone__img”
key={i}
style={{backgroundImage:’url(${file.preview})’}} >
< div className=”fileName”>{file.Name}< / div>
< Icon
className=”heading-icon”
color=”error”
onClick={()=>this.removeFile(i,file)} >
delete
< / Icon>
< / div >
)
)
:null}
< div className=”file__add”>
{“”}
< label > < FontAwesomeIcon className=”file_plus_Icon” icon = “plus”/ > < /label >
< input
id =”file”
type=”file”
className=”hideFileText”
multiple
onChange={(e)=>this.appendFiles(e.targetFiles)}
/ >
< / div >
< / div >
< / div >
);
}
appendFiles=(files)=>{
let filesArray=this.state.filesArray ? this.state.filesArray :[]
for(let f1 in files){
if(files[f1].type){
files[f1][“preview”]=URL.createObjectURL(files[f1]);
filesArray.push(files[f1]);
}
}
this.setState({filesArray,displayAttachmentsModal:true})
};
//remove selected files
removeFile=(ind,file)=>{
let filesArray=this.state.filesArray;
//delete filesArray[ind]
filesArray.splice(ind,1);
if(filesArray && filesArray.length &&filesArray.length >0){
this.setState({filesArray});
}else{
this.setState({filesArray,displayAttachmentsModal:false})
}};
}
export default App;In state , we declare two state variables
state = {
filesArray: [],
progressData: {},
};
filesArray for storing Images ,progress data for storing download percentage.
//after selecting files append recent files to previous files
appendFiles = (files) => {
let filesArray = this.state.filesArray ? this.state.filesArray : [];
for (let fl in files) {
if (files[fl].type) {
files[fl][“preview”] = URL.createObjectURL(files[fl]);
filesArray.push(files[fl]);
}
}
this.setState({ filesArray });
};
In append files, For every time we upload a new Image it pushes that image into the files Array and it sets the state of the file array. Before pushing into the array, we added the preview to that Image for local display
//remove selected files
removeFile = (ind, file) => {
let filesArray = this.state.filesArray;
// delete filesArray[ind];
filesArray.splice(ind, 1);
if (filesArray && filesArray.length && filesArray.length > 0) {
this.setState({ filesArray });
} else {
this.setState({ filesArray, displayAttachmentsModal: false });
}
};
}
In removeFile , when we wanted to remove an unwanted Image file then we call this function and remove that image, When calling that image we send two arguments ie index and file, Using that index, we splice the array
To upload an image, we have to use API calls
To upload a single image, we use FormData in that we append a file of binary format image
For example
let formData = new FormData();
// and body which consists of binary image
formData.append(‘file’, body);
//Sending to server
const axios = require(‘axios’)
axios.post(apiCall, body,headers)
.then( (response)=> {
console.log(response);
})
To upload multiple we have to hit the server multiple times, for supposing we have to upload 5 images, we have to hit sever 5 times
We have to iterate in the loop five times
Here I used
Here files=array of images
Here we use a map for iteration
if(files && files.length){
files.map((item, index) => {
let fileId = `${item.name}-${item.lastModified}`;
let loginData = JSON.parse(localStorage.getItem(‘loginCredentials’));
let headers = {
‘Accept’: ‘application/json’,
‘Authorization’: `Bearer ${loginData.accessToken}`,
};
let appendFielData = {};
let fileUrl = ”
let body = item.slice(0, item.size + 1)
fileUrl = `${apiUrl}uploads/files?preview=${item.preview}&size= ${item.size.toString()}&fileId=${fileId}&mimetype=${item.type}&name=${item.name}&uploadPath=docs`
const axios = require(‘axios’)
let body = new FormData();
body.append(‘file’, item);
return axios.post(fileUrl, body,headers)
.then( (response)=> {
console.log(response);
socket.on(‘progress’, (response => {
console.log(‘socket ————- response’, response)
let filesArray = this.state.filesArray
// Now I comapare the names from response of both socket and axios response
filesArray.forEach((item, index) => {
if (item.name == response.name) {
// I assign perccentage data to files array
item.percentage = response.percentage
}
if (item.percentage == 100) {
// if percentage reaches to 100% we splice the item from array
filesArray.splice(index, 1)
}
})
this.setState({ filesArray: filesArray })
}))
})
})
}
—> In fileUrl :
apiUrl : which server I am going to hit
Item.preview: we have a local blob URL to display a preview image
Item.size: we provide the size of the file
fileId: we provide file Ids which are unique
Item.type: type of a file,
Item.name: name of a file,
docs: upload path, where we store in database
—> Generally, we used FormData() for uploading
In that we append the data like
Let formData=new FormData()
formData.append(āfileā,body) // Here body is body of file
CSS File
.onDragOver {
width: 100% !important;
position: relative !important;
left: 50%;
bottom: -51%;
color: white;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background: #fff;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
// height: 261px;
width: 100%;
font-family: sans-serif;
border: 1px solid #efefef;
border-radius: 5px;
box-sizing: border-box;
box-shadow: 0 5px 10px #efefef;
overflow: hidden;
}
.fileName {
color: black;
position: absolute;
font-size: 12px;
text-align: center;
transition: all 0.3s;
z-index: 10;
width: 100%;
line-height: 12px;
margin: 0;
top: calc(50% – 6px);
}
.fileDelete {
transition: all 0.3s;
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
color: red;
font-size: 15px;
text-transform: uppercase;
}
.file_add {
position: relative;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-flex-grow: 0;
flex-grow: 0;
-webkit-flex-shrink: 0;
flex-shrink: 0;
overflow: hidden;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
// background-color: #8a8d91;
border-color: transparent;
text-align: left;
border-width: 0px;
// height: 100px;
// width: 300px;
border-radius: 6px;
padding: 0px;
cursor: pointer;
border-style: solid;
margin-right: 15px;
margin-left: 15px;
}
.file_Plus_Icon {
position: relative;
display: inline;
flex-grow: 0;
flex-shrink: 0;
overflow: hidden;
white-space: pre-wrap;
overflow-wrap: break-word;
height: 24px;
font-size: 24px;
color: rgb(255, 255, 255);
background-color: rgba(0, 0, 0, 0);
font-family: SkypeAssets-Light;
padding: 0px;
cursor: pointer;
}
Backend Code:
Technology
- Nodejs
- Websockets
Here iām using uploads variable as an object, creating a property based on fileId in uploads object and deleting it once the process is completed.
const fs = require(‘fs’);
import socket from “./socket”
let uploads = {};
async function uploadFiles(req, res) {
let fileId = req.query[‘fileId’];
let startByte = 0;
let name = req.query[‘name’];
let mimetype = req.query[‘mimetype’];
let fileSize = parseInt(req.query[‘size’], 10);
let perc;
console.log(‘file Size’, fileSize, fileId, startByte);
let file = {
preview: req.query[“preview”],
size: req.query[“size”],
name: req.query[“name”],
type: req.query[“mimetype”]
}
if (!fileId) {
res.writeHead(400, “No file id”);
res.end(400);
}
if (!uploads[fileId])
uploads[fileId] = {};
let upload = uploads[fileId];
Here config.path in the sense path where we are uploading the file, from the query I’m getting uploadPath, it is a directory where I should place a file. āfs.existsSync()ā checking whether directory exist or not, if not creating a directory using āfs.mkdirSync()ā
req.uploadPath = req.query.uploadPath;
if (req.query.uploadPath && req.query.directory) {
if (!fs.existsSync(config.path + req.uploadPath)) {
fs.mkdirSync(config.path + req.uploadPath)
}
req.uploadPath = req.uploadPath + “/” + req.query.directory;
}
if (!fs.existsSync(config.path + req.uploadPath)) {
fs.mkdirSync(config.path + req.uploadPath)
}
We have imported fs in the beginning of the code.
let fileStream;
if (!startByte) {
upload.bytesReceived = 0;
// This create a writable stream to the path
fileStream = fs.createWriteStream(config.path + req.uploadPath + “/” + name, {
flags: ‘w’
});
} else {
fileStream = fs.createWriteStream(config.path + req.uploadPath + “/” + name, {
flags: ‘a’
});
}
From content-length we will get the total size of a file and we saving that data in total variable, from āreq.on(ādata;ā)ā we will get information by listening to the data stream, ādata.lengthā will give us how much data has been processed from there we are calculating the completed percentage of the file using perc = parseInt((upload.bytesReceived / total) * 100);
Emit call for event āprogressā, here we emit an object response, how much data has been processed in percentages. After completion of the data stream we call pipe stream , This pipes the post data to the file
var total = req.headers[‘content-length’];
req.on(‘data’, function (data) {
upload.bytesReceived += data.length;
perc = parseInt((upload.bytesReceived / total) * 100);
let response = {
text: ‘percent complete: ‘ + perc + ‘%\n’,
percentage: perc,
name: name,
mimetype: mimetype,
path: config.path + req.uploadPath + “/” + name,
filePath: req.uploadPath + “/” + name,
file: file
}
socket.emit(‘progress’, response);
});
req.pipe(fileStream);
When the request is finished we call a close stream to check whether data processed completely or not .
If bytes received while processing file equals to total bytes of the file which we get from API header from starting. If both are equal we send a response text with some data which we required for further operations. If both bytes are not the same, it means while processing data is corrupted, we send an event with the same event name āprogressā like file unfinished. If there is an error in close, like input and output errors, an error stream will be called and ends the process by sending an error response.