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

Devin Ekadeni
6 min readDec 24, 2020

--

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

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

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

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:

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:

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:

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

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

client/src/components/UploadItem/UploadItem.js

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

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
}

Then change the existing hardcoded status with this variable.

client/src/redux/uploadFile/uploadFile.reducer.js
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
// 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:
...
// 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.

client/src/components/UploadProgress/UploadProgress.js
client/src/components/UploadItem/UploadItem.js

Let’s test it out:

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>
)
...
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 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! 🎉🎉

Sign up to discover human stories that deepen your understanding of the world.

--

--

Devin Ekadeni
Devin Ekadeni

Written by Devin Ekadeni

Software Engineer, mostly work on Javascript: ReactJS, NodeJS, Redux, Express, Webpack

No responses yet

Write a response