Home > Back-end >  How do I cancel all pending Axios requests in React Native?
How do I cancel all pending Axios requests in React Native?

Time:03-09

My React Native app uses axios to connect to a backend. This is done in the file myApi.js:

class client {
  axiosClient = axios.create({
    baseURL: example.com,
  });

  async get(url, data, config) {
    return this.axiosClient.get(url, data, config);
  }

  async put(url, data, config) {
    return this.axiosClient.put(url, data, config);
  }

  async post(url, data, config) {
    return this.axiosClient.post(url, data, config);
  }
}

export default new client();

I have a component which contains a useEffect which is controlled by a date picker. The selected date is held in a context called DateContext. When the selected date changes, a request is fired off to get some data for that date. When data is returned, it is displayed to the user.

The Component is:

const DaySelect = () => {
  const {dateState} = useContext(DateContext);

  useEffect(() => {
    const load = () => {

      const url = '/getDataForDate';

      const req = {
        selectedDate: moment(dateState.currentDate).format(
          'YYYY-MM-DD',
        ),
      };

      myApi
        .post(url, req)
        .then((res) => {
          // Now put results into state so they will be displayed
        })
    };

    load();

  }, [dateState.currentDate]);

  return (
    <View>
        <>
          <Text>Please select a date.</Text>
          <DateSelector />
          <Results />
        </>
      )}
    </View>
  );
};

export default DaySelect;

DateSelector is just the Component where the date is selected; any change to the date updates the value of dateState.currentDate. Results displays the data.

This works fine as long as the user clicks a date, and waits for the results to show before clicking a new date. However, if they click several times, then a request is fired off each time and the resulting data is displayed as each request completes. Since the requests don't finish in the order that they start, this often results in the wrong data being shown.

To avoid this, I believe I need to cancel any existing requests before making a new request. I've tried to do this using Abort Controller, but it doesn't seem to do anything.

I added the following to myApi.js:

const controller = new AbortController();

class client {
  axiosClient = axios.create({
    baseURL: example.com,
  });

  async get(url, data, config) {
    return this.axiosClient.get(url, data, config);
  }

  async put(url, data, config) {
    return this.axiosClient.put(url, data, config);
  }

  async post(url, data, config) {
    return this.axiosClient.post(url, data, config, {signal: controller.signal});
  }

  async cancel() {
     controller.abort();
  }
}

export default new client();

Then in my main component I do

myApi.cancel()

before making the new request.

This doesn't seem to do anything, though - the requests don't get cancelled. What am I doing wrong here?

EDIT: Following Ibrahim's suggestion below, I changed the api file to:

const cancelTokenSource = axios.CancelToken.source();

class client {
  axiosClient = axios.create({
    baseURL: example.com,
  });

  async post(url, data, config) {
    const newConfig = {
       ...config,
       cancelToken: cancelTokenSource.token
    };
    return this.axiosClient.post(url, data, newConfig);
  }

  async cancel() { // Tried this with and without async
     cancelTokenSource.cancel();
  }
}

export default new client();

This makes the api call fail entirely.

CodePudding user response:

Step1: Generate cancel token

const cancelTokenSource = axios.CancelToken.source();

Step2: Assign cancel token to each request

axios.get('example.com/api/getDataForDate', {
  cancelToken: cancelTokenSource.token
});

// Or if you are using POST request

axios.post('example.com/api/postApi', {data}, {
  cancelToken: ancelTokenSource.token,
});

Step3: Cancel request using cancel token

cancelTokenSource.cancel();

CodePudding user response:

Posting the solution I found in case it helps someone else.

In myApi I used AbortController to ensure that any cancellable requests are aborted when a new cancellable request comes in:

let controller = new AbortController();

class client {
  axiosClient = axios.create({
    baseURL: example.com,
  });

  async post(url, data, config, stoppable) {
    let newConfig = {...config};

    // If this call can be cancelled, cancel any existing ones
    // and set up a new AbortController
    if (stoppable) {
      if (controller) {
        controller.abort();
      }
      // Add AbortSignal to the request config
      controller = new AbortController();
      newConfig = {...newConfig, signal: controller.signal};
    }
    return this.axiosClient.post(url, data, newConfig);
  }
}

export default new client();

Then in my component I pass in 'stoppable' as true; after the call I check whether the call was aborted or not. If not, I show the results; otherwise I ignore the response:

  useEffect(() => {
    const load = () => {

      const url = '/getDataForDate';

      const req = {
        selectedDate: moment(dateState.currentDate).format(
          'YYYY-MM-DD',
        ),
      };

      myApi
        .post(url, req, null, true)
        .then((res) => {
          if (!res.config.signal.aborted) {
            // Do something with the results
          }
        })
        .catch((err) => {
// Show an error if the request has failed entirely
        });
    };

    load();
  }, [dateState.currentDate]);
  • Related