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:
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:
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
:
And then we modify the uploadFile
action to add property cancelToken
to the axios from the source
instance that we have defined:
Now let's update the component UploadItem
to test the cancellation function:
Now all ready, let's try to cancel the upload in the middle of process.
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.
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.
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>
)
...
And there you go, now you can test it as the final version of the apps, it should be like this:
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)
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.
mantap