Reputation: 1075
In my app I have a button which with every click upload new file on the server (axios POST method). During uploading I have another button which canceles that request.
Problem: If I have several active uploads and click on the cancel button of one of them, only the last request will be canceled. I.e if I want to cancel second uploading of three total, third will be the one canceled.
Question: How to fix that and by clicking on cancel button abort only current request (the one whose cancel button was clicked)?
Here is an interface screenshot:
Here is my simplified code:
Wrapper with upload button:
import React from 'react';
import Types from 'prop-types';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
MenuItem,
TextField,
Select,
InputLabel,
} from '@material-ui/core';
import FormControl from '@material-ui/core/FormControl';
import Button from '@material-ui/core/Button';
import FileInput from 'components/Inputs/FileInput';
import ProgressBar from 'components/ProgressBar/ProgressBar';
import axios from 'axios';
class CreateDocumentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
fieldValue: null,
file: {},
requestCounter: 0,
requestArr: [],
showTitle: false,
}
}
/* When button been clicked one more time - add new upload progress bar */
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.requestCounter !== this.state.requestCounter) {
this.setState({
requestArr: this.state.requestArr.concat(
<ProgressBar
fileData={this.state.fileData}
cutTitle={this.cutTitle}
/>
)
});
}
}
render() {
const { open, cancel, row, submit, onFieldChange } = this.props;
const { data } = this.state;
return (
<Dialog open={open} onClose={cancel}>
<DialogTitle>{'Прикрепить новый документ'}</DialogTitle>
<DialogContent>
<DialogActions>
<Button color="secondary" onClick={() => {
this.incrementCounter();
this.changeTitleVisibility(false);
}}>
{'Upload'}
</Button>
<Button color="primary" disabled={!(row.file && row.number)} onClick={submit}>
{'Save'}
</Button>
<Button color="secondary" onClick={cancel}>
{'Back'}
</Button>
</DialogActions>
{this.state.requestArr && this.state.requestArr.map((item, index) => item)}
</DialogContent>
</Dialog>
);
}
};
export default CreateDocumentComponent;
Module with progress bar and uploading functionality:
import React from 'react';
import {IconButton} from "@material-ui/core";
import axios from "axios";
import CancelIcon from '@material-ui/icons/Cancel';
import { withStyles, styled } from '@material-ui/styles';
const MyIconButton = styled(IconButton)({
paddingTop: '0',
paddingBottom: '0'
});
// Initiating cancel token for each upload
const CancelToken = axios.CancelToken;
let cancel;
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.state = {
percentage: 0,
status: '',
res: '',
}
};
// when module is mounted - make a request
componentDidMount() {
const { fileData } = this.props;
let dataa = new FormData();
dataa.append('data', fileData);
dataa.append('number', 3434);
dataa.append('taskId', 157530);
const config = {
cancelToken: new CancelToken(c => {
// this function will receive a cancel function as a parameter
cancel = c;
}),
onUploadProgress: progressEvent => {
let percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total );
this.changePercentage(percentCompleted);
}
};
this.changeStatus('');
axios
.post(`/bpm/attachments`, dataa, config)
.then(res => {
if (res) {
this.changeResult(res.data);
this.changeStatus('Loaded');
} else {
this.changeStatus('Canceled');
this.changePercentage(100);
}
})
.catch(error => {
this.changeStatus('Error');
});
};
cancelRequest = () => {
cancel('Loading is canceled');
};
changePercentage = val => {
this.setState({
percentage: val,
});
};
changeStatus = val => {
this.setState({
status: val
});
};
changeResult = val => {
this.setState({
res: val
});
};
render() {
const { classes, fileData, cutTitle } = this.props;
return (
<React.Fragment>
<div className={classes.progressBlock} style={{'display' : fileData ? 'block' : 'none'}}>
<div className={classes.fileName}>{(fileData && (this.state.percentage > 0)) ? cutTitle(fileData.name) : ''}</div>
<progress className={`
${classes.progressBar} ${this.state.status === 'Canceled'
? classes.progressBarCanceled
: this.state.status === 'Error'
? classes.progressBarError
: ''}
`} value={this.state.percentage} max="100"></progress>
<MyIconButton style={{
'display': this.state.status.length ? 'none' : 'inline-block'
}} onClick={() => this.cancelRequest()} title="Canceled">
<CancelIcon />
</MyIconButton>
<div className={classes.uploadStatus} style={
{'color': (this.state.status === 'Canceled' || this.state.status === 'Error') ? '#f50057' : 'rgba(0, 0, 0, 0.87)'}
}>{this.state.status}</div>
</div>
</React.Fragment>
);
}
}
export default withStyles(styles)(ProgressBar);
Upvotes: 3
Views: 2363
Reputation: 7292
It's because you create cancelToken outside of the component and rewrite it for every component instance. Here is the fix:
Wrapper component:
let CancelToken = axios.CancelToken;
<ProgressBar
cancelToken={CancelToken}
/>
ProgressBar component:
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.cancel;
};
const config = {
cancelToken: new cancelToken(c => {
this.cancel = c;
}),
onUploadProgress: progressEvent => {
let percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total );
this.changePercentage(percentCompleted);
}
};
axios.post(/bpm/attachments, dataa, config)
.then(res => {})
.catch(error => {})
cancelRequest = () => {
this.cancel('Request is canceled');
};
Upvotes: 3