Sergey
Sergey

Reputation: 1075

Cancel only current axios request in React application

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:

enter image description here

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

Answers (1)

Andy K
Andy K

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

Related Questions