Multiple Files Upload With A Progress Bar

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
POSTuploadsTo upload an image
GET/path/filenameTo display an image
Download/path/filenameTo 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.

Leave A Comment

Your email address will not be published. Required fields are marked *