How to Upload Multiple File With Feature Cancellation & Retry Using ReactJS
Tags: react, fileupload, cancelupload, retryupload
Published: 2020-12-23 11:11:55

> _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)][link_part-1]_

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][link_part-1].

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

![Final Result][gif_final-result]
<figcaption>Final result of this tutorial - uploading multiple files with feature cancellation & retry upload</figcaption>

If you want to look at the source code, you can access it [here][link_source-code]. And if you want to see the changes only from part 1, you can see the commit [here][link_part1-diff], 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][link_part-1] article. The main tools that we use for the cancellation & retry upload are:
- [ReactJS][link_react]
- [Redux][link_redux]
- [Redux-thunk][link_thunk]
- [Axios][link_axios]

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.

```bash
$ git clone https://github.com/devinekadeni/my-blog.git
$ cd my-blog/upload-multiple-file-with-progress-bar
```

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

```bash
$ cd server
$ npm install
$ npm start
```

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][link_concurrent], but I won't be using it now._

The next step let's serve the client side
```bash
$ cd client
$ npm install
$ npm start
```
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][gif_final-result-part1]
<figcaption>Final result of part 1 - uploading multiple files with progress bar</figcaption>

> _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][link_stackoverflow-network]_

## 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][link_axios-cancellation] 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][img_cancel-token]
<figcaption>client/src/redux/uploadFile/uploadFile.utils.js</figcaption>

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

![Axios Canceltoken][img_axios-cancel-token]
<figcaption>client/src/redux/uploadFile/uploadFile.actions.js</figcaption>

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

![Upload Item component][img_upload-item-cancel-1]
<figcaption>client/src/components/UploadItem/UploadItem.js</figcaption>

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

![Cancel functionality][gif_cancel-functionality]
<figcaption>Cancellation functionality</figcaption>

## 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:

```javascript
// client/src/constants.js

export const STATUS_UPLOAD = {
  uploading: 0,
  success: 1,
  failed: 2
}
```

Then change the existing hardcoded status with this variable.

![Reducer status change][img_status-reducer]
<figcaption>client/src/redux/uploadFile/uploadFile.reducer.js</figcaption>

![Utils status change][img_status-utils]
<figcaption>client/src/redux/uploadFile/uploadFile.utils.js</figcaption>

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

```javascript
// client/src/redux/uploadFile/uploadFile.type

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

export default uploadFileTypes
```

```javascript
// 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:
...
```

```javascript
// 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))
}
```

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][img_upload-wrapper]
<figcaption>client/src/components/UploadProgress/UploadProgress.js</figcaption>

![Upload Item retry][img_upload-item-retry]
<figcaption>client/src/components/UploadItem/UploadItem.js</figcaption>

Let's test it out:

![Pre-final result][gif_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:

```javascript
// 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>
  )
...
```

![Final CSS uploaditem](https://dev-to-uploads.s3.amazonaws.com/i/7up60mjv84jyizottow3.png)
<figcaption>client/src/components/UploadItem/UploadItem.module.css</figcaption>

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

![Final Result][gif_final-result]
<figcaption>Final Result Application</figcaption>

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][link_source-code].

Happy Coding! 🎉🎉

<!-----------------Resource Part----------------->
<!--Link-->
[link_part-1]: https://dev.to/devinekadeni/how-to-upload-multiple-file-with-progress-bar-reactjs-redux-and-expressjs-4hb3
[link_source-code]: https://github.com/devinekadeni/my-blog/tree/master/cancel-and-retry-upload-multiple-file
[link_part1-diff]: https://github.com/devinekadeni/my-blog/commit/e1c7759c31a5862ebbc4a39d76cc7872f67c5f48
[link_concurrent]: https://github.com/kimmobrunfeldt/concurrently
[link_react]: https://reactjs.org/
[link_redux]: https://redux.js.org/
[link_thunk]: https://github.com/reduxjs/redux-thunk
[link_axios]: https://github.com/axios/axios
[link_axios-cancellation]: https://github.com/axios/axios#cancellation
[link_stackoverflow-network]: https://stackoverflow.com/questions/41088022/how-to-get-onuploadprogress-in-axios/48412965#48412965

<!--Gif-->
[gif_final-result]: https://dev-to-uploads.s3.amazonaws.com/i/t11u2lt1jq2scpxrfna1.gif
[gif_final-result-part1]: https://dev-to-uploads.s3.amazonaws.com/i/ytydzo6r3y7f7bfdy3f0.gif
[gif_cancel-functionality]: https://dev-to-uploads.s3.amazonaws.com/i/r5m1sddmx13kq8yh3xdz.gif
[gif_pre-final-result]: https://dev-to-uploads.s3.amazonaws.com/i/evp0y01g2qb90bhpfcj7.gif

<!--Image-->
[img_cancel-token]: https://dev-to-uploads.s3.amazonaws.com/i/hosv2rz9jmjfnnvrvmgr.png
[img_axios-cancel-token]: https://dev-to-uploads.s3.amazonaws.com/i/3nltjt2jnebxhvbeb0ku.png
[img_upload-item-cancel-1]: https://dev-to-uploads.s3.amazonaws.com/i/k9ip4zu46u45odouiep1.png
[img_status-reducer]: https://dev-to-uploads.s3.amazonaws.com/i/2tx4upzm8pjc0y6a8dur.png
[img_status-utils]: https://dev-to-uploads.s3.amazonaws.com/i/fq3r4az6j2bvlr647an4.png
[img_upload-wrapper]: https://dev-to-uploads.s3.amazonaws.com/i/219qarfzm5o814lckvtt.png
[img_upload-item-retry]: https://dev-to-uploads.s3.amazonaws.com/i/9nvochseyaxyoocif7v2.png