DEV Community

Cover image for How to Upload Multiple File With Feature Cancellation & Retry Using ReactJS
Devin Ekadeni
Devin Ekadeni

Posted on

How to Upload Multiple File With Feature Cancellation & Retry Using ReactJS

This is part 2 of How to Upload Multiple File, you can find the part 1 here --> How to Upload Multiple File with Progress Bar (ReactJS + Redux and ExpressJS)

If you haven't read the part 1, I suggest you to read it first in order to follow along this article easier. And also I will only specifically talk about the cancellation & retry upload feature in this article, the multiple upload mechanism is explained on part 1.

Now let's get started, firstly I will show you the final result that we want to achieve:

Final Result

Final result of this tutorial - uploading multiple files with feature cancellation & retry upload

If you want to look at the source code, you can access it here. And if you want to see the changes only from part 1, you can see the commit here, otherwise I will explain it step by step on below.

Get Started

First thing first, please use the data structure as same as the part 1 article. The main tools that we use for the cancellation & retry upload are:

and we use the rest tools as same as in the part 1 article.

Now let's start creating the project folder, I will clone the source code from part 1 and we will develop from that base.

$ git clone https://github.com/devinekadeni/my-blog.git
$ cd my-blog/upload-multiple-file-with-progress-bar
Enter fullscreen mode Exit fullscreen mode

For now let's just run the backend server right away, since we have no modification on the backend side.

$ cd server
$ npm install
$ npm start
Enter fullscreen mode Exit fullscreen mode

Now the server is ready on http://localhost:5000

Don't forget to open 2 terminal in order to run server and the client side simultaneously. Though you can run it with 1 terminal if you want using concurrent, but I won't be using it now.

The next step let's serve the client side

$ cd client
$ npm install
$ npm start
Enter fullscreen mode Exit fullscreen mode

Now the client is ready on http://localhost:3000

At the current code, you will be able to upload multiple file with progress bar like so:

Part 1 result

Final result of part 1 - uploading multiple files with progress bar

Don't forget to change the network into slow 3G to be able to see the movement of the progress bar, I'll put the detail of the reason here

Set axios cancel source on upload item

Okay it's time to dive in into the matter, let's start with the cancellation feature. For reference, since we're using axios for http request, they even support for the cancellation mechanism from its documentation, you can check it here hence we will be using it for our cancellation when uploading the file.

As you read on the documentation, on each http request, axios require field cancelToken to contain value source.token then if you want to cancel the request, you could just invoke source.cancel('cancel message'), as simple as that.
The challenge is, where do we define this source instance, could you guest?
Thankfully with our data structure, we could just define it on each object file which live in redux store.
Since we set the file data every time user insert new file, we can define the source instance inside the redux/uploadFile/uploadFile.utils.js:

canceltoken

client/src/redux/uploadFile/uploadFile.utils.js

And then we modify the uploadFile action to add property cancelToken to the axios from the source instance that we have defined:

Axios Canceltoken

client/src/redux/uploadFile/uploadFile.actions.js

Now let's update the component UploadItem to test the cancellation function:

Upload Item component

client/src/components/UploadItem/UploadItem.js

Now all ready, let's try to cancel the upload in the middle of process.

Cancel functionality

Cancellation functionality

Setup Retry Feature

Now before we setup the retry functionality, let's first create a constant data to specify the upload item status, so that we have 1 single source of status data:

// client/src/constants.js

export const STATUS_UPLOAD = {
  uploading: 0,
  success: 1,
  failed: 2
}
Enter fullscreen mode Exit fullscreen mode

Then change the existing hardcoded status with this variable.

Reducer status change

client/src/redux/uploadFile/uploadFile.reducer.js

Utils status change

client/src/redux/uploadFile/uploadFile.utils.js

Nice, now let's start creating the retry functionality by defining the retryUpload action & action creators

// client/src/redux/uploadFile/uploadFile.type

const uploadFileTypes = {
...
  RETRY_UPLOAD_FILE: 'RETRY_UPLOAD_FILE',
}

export default uploadFileTypes
Enter fullscreen mode Exit fullscreen mode
// client/src/redux/uploadFile/uploadFile.reducer.js

import axios from 'axios'
...
   case uploadFileTypes.RETRY_UPLOAD_FILE:
      const CancelToken = axios.CancelToken
      const cancelSource = CancelToken.source()

      return {
        ...state,
        fileProgress: {
          ...state.fileProgress,
          [action.payload]: {
            ...state.fileProgress[action.payload],
            status: STATUS_UPLOAD.uploading,
            progress: 0,
            cancelSource,
          }
        }
      }

   default:
...
Enter fullscreen mode Exit fullscreen mode
// client/src/redux/uploadFile/uploadFile.actions.js

...
export const retryUpload = (id) => (dispatch, getState) => {
  dispatch({
    type: uploadFileTypes.RETRY_UPLOAD_FILE,
    payload: id,
  })

  const { fileProgress } = getState().UploadFile

  const reuploadFile = [fileProgress[id]]

  dispatch(uploadFile(reuploadFile))
}
Enter fullscreen mode Exit fullscreen mode

So I will explain a little bit regarding those 3 file changes.
First we define the action creator type for retry upload
Second we define the reducer to handle type RETRY_UPLOAD_FILE, in here we reset the file.progress to 0, file.status to STATUS_UPLOAD.uploading and we re-instantiate the cancelSource from axios, so that it can be used again later.
Third we define retryUpload action which will dispatch RETRY_UPLOAD_FILE and then reupload the file again by dispatching uploadFile action. Notice in here we define the reuploadFile into array because action uploadFile only receive array variable.

Now let's modify the UploadItem component to support retry upload function.

Upload Progress

client/src/components/UploadProgress/UploadProgress.js

Upload Item retry

client/src/components/UploadItem/UploadItem.js

Let's test it out:

Pre-final result

Great! It works perfectly as we want to. Now to make the UI a bit more beautiful, let's give it a final touch:

// client/components/UploadItem/UploadItem.js

import React, { useMemo } from 'react'
...
const UploadItem = props => {
...
  const renderIcon = useMemo(() => {
    const cancelUpload = () => {
      cancelSource.cancel('Cancelled by user')
    }

    if (status === STATUS_UPLOAD.uploading) {
      return (
        <span
          title="Cancel upload"
          style={{ color: 'red' }}
          onClick={cancelUpload}
        >
          
        </span>
      )
    } else if (status === STATUS_UPLOAD.success) {
      return (
        <span
          title="Success upload"
          style={{ color: 'green', cursor: 'initial' }}
        >
          
        </span>
      )
    } else if (status === STATUS_UPLOAD.failed) {
      return (
        <span
          title="Retry upload"
          style={{ color: 'orange' }}
          onClick={props.retryUpload}
        >
          ↩︎
        </span>
      )
    }

    return null
  }, [status])

  return (
    <div className={Styles.wrapperItem}>
      <div className={Styles.leftSide}>
        <div className={Styles.progressBar}>
          <div style={{ width: `${progress}%` }} />
        </div>
        <label>{file.name}</label>
      </div>
      <div className={Styles.rightSide}>
        {renderIcon}
        <span>{progress}%</span>
      </div>
    </div>
  )
...
Enter fullscreen mode Exit fullscreen mode

Final CSS uploaditem

client/src/components/UploadItem/UploadItem.module.css

And there you go, now you can test it as the final version of the apps, it should be like this:

Final Result

Final Result Application

Voila! That's it! We've reached at the end of this tutorial. You can take a look at the full source code if you want here.

Happy Coding! 🎉🎉

Top comments (2)

Collapse
 
yatesh profile image
yateshchhabra1995 • Edited

Hello Devin,
I used your code, In this when the upload is complete or its 100% when we click on that upload it will get upload again. How can we prevent it ? One more thing I wanna ask is if the upload is done it gets remove from that panel or we can give button to remove that specific upload.

Collapse
 
iswiboni profile image
ISWIBONI

mantap