Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - requests cancellation on page change

I'm using axios for fetching/ posting the data to my server and my current approach looks like this:

1. Request are made using redux actions with redux-thunk, and an action looks like this:

export const getRecords = () => (dispatch, getState, { api }) => {
  const type = GET_RECORDS_LOAD;
  return dispatch({
    type,
    promise: api.Records.get({ type }),
  });
};

2. api.Record.get looks like this:

import _ from 'lodash';
import axios from 'axios';

const APIInstance = axios.create({
  baseURL: process.env.API_URL,
});

const getCancelToken = id => new axios.CancelToken((c) => {
  const cancel = c;
  const cancelationTokens = _.get(window, 'cancelationTokens', {});
  cancelationTokens[id] = cancel;
  _.set(window, 'cancelationTokens', cancelationTokens);
});

const api = {
  Records: {
    get: ({ type }) => APIInstance.get('/my-records', { cancelToken: getCancelToken(type) }),
  },
};

Here I create a cancelToken based on the redux action type and I'm storing it in a window.cancelationTokens object, so they can be cancelled anywhere from the app.

3. Cancelling on componentWillUnmount

import * as Types from './path/to/action/types';

const cancelToken = (type) => {
  const cancel = _.get(window, `cancelationTokens.${type}`);
  if (!cancel) return;
  cancel();
}

componentWillUnmount() {
  cancelToken(Types.GET_RECORDS_LOAD);
  // If more request I have to cancel them manually...
}

As you can see, there is no major problem with this approach, but if I do plenty of requests on one page, I have to cancel them all manually in the componentWillUnmount.

My questions:

  1. Is there a way to automatically cancel the ongoing request if user changes the page in my app?
  2. If yes - is it the correct way to do this, or are there easier way to cancel requests?
like image 987
mdmb Avatar asked Oct 02 '18 11:10

mdmb


People also ask

How do I cancel a request on react?

One signal from the AbortController() object can be used to abort/cancel multiple API requests. This means that if we have more than one API request in our component(regardless of the method, POST, GET, DELETE e.t.c), we can use one source variable to cancel all the requests.

How do I stop API call in react?

You can wrap XItem component with React. memo: const XItem = (props) => { ... } const areEqual = (prevProps, nextProps) => { /* Add your logic here to check if you want to rerender XItem return true if you don't want rerender return false if you want a rerender */ } export default React.

How do I cancel http request?

We can use the AbortController object and the associated AbortSignal with the Fetch API to make cancelable HTTP requests. Once the AbortSignal is sent, the HTTP request is canceled and won't be sent if the cancellation signal is sent before the HTTP request is done.


1 Answers

So, what I did is I created a class called RequestCancelation that uses history package. It can cancel the requests based on the passed action type, or based on the history.location.pathname.

RequestCancelation.js

import _ from 'lodash';
import axios from 'axios';
import createHistory from 'history/createBrowserHistory';

// In my case the history is imported from another file, as I pass
// it to the `Router` from `react-router-dom`. For the purpose of this
// example I created the history here.
const history = createHistory();

class RequestCancelation {
  static constants = {
    cancelationTokens: 'CANCELATION_TOKENS',
  }

  getTokens() {
    return _.get(window, RequestCancelation.constants.cancelationTokens, {});
  }

  setTokens(tokens) {
    return _.set(window, RequestCancelation.constants.cancelationTokens, tokens);
  }

  deleteTokens(key) {
    if (!key) return undefined;
    delete window[RequestCancelation.constants.cancelationTokens][key];
    return this.getTokens();
  }

  getLocationKey() {
    return _.get(history, 'location.pathname');
  }

  getCancelToken(type) {
    return new axios.CancelToken((c) => {
      const cancel = c;
      if (typeof window === 'undefined') return;
      const tokens = this.getTokens();
      if (type) {
        tokens[type] = cancel;
      } else {
        const key = this.getLocationKey();
        if (!key) return;
        if (!tokens[key]) tokens[key] = [];
        tokens[key].push(cancel);
      }
      this.setTokens(tokens);
    });
  }

  cancelRequest(type) {
    if (!type) {
      return console.warn('#cancelRequest - please specify \'type\'');
    }
    if (typeof window === 'undefined') return undefined;
    const tokens = this.getTokens();
    const cancel = tokens[type];
    if (!cancel) return undefined;
    cancel();
    return this.deleteTokens(type);
  }

  cancelRequests() {
    if (typeof window === 'undefined') return undefined;
    const tokens = this.getTokens();
    const key = this.getLocationKey();
    if (!key) return undefined;
    const cancels = tokens[key];
    if (!cancels) return undefined;
    cancels.forEach(cancel => cancel());
    return this.deleteTokens(key);
  }

  clearTokens() {
    if (typeof window === 'undefined') return undefined;
    window[RequestCancelation.constants.cancelationTokens] = {};
    return this.getTokens();
  }
}

const cancelation = new RequestCancelation();

export default cancelation;

Hope this helps someone and maybe someone can improve it :)

Also available as a gist.

like image 161
mdmb Avatar answered Oct 05 '22 23:10

mdmb