Cancel requests on route change (React/Redux/Axios)

Viewed 2297

Is there convenient way to cancel all sending request on any route changes using axios, redux-thunk, redux? I know that axios has cancellation token which should be added to every request and I can call source.cancel(/* message */) to cancel it.

P.S. Currently I handle this in componentWillUnmount. Maybe there is something better?

3 Answers

Very simple solution would be to declare isMounted variable and set it to false when component unmounts.

Other way of handling this issue is, (I'm not sure about axios but) XMLHttpRequest has abort method on it. You could call xhr.abort() on componentWillUnmount. Check it here: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort

And finally there is an industry solution :) from Netflix UI engineers. They have written a redux middleware for RxJS and using RxJS operators to fire up and cancel the requests.

The library is called redux-observable. I recommend to take a look at examples: https://redux-observable.js.org/docs/recipes/Cancellation.html

You can watch the talk about it here: https://youtu.be/AslncyG8whg

I found a simple solution (without redux) to do that. All you need is a cancelToken in your axios requests. After, use the useHook to detect route changes. Then, cancel the requests with the TOKEN when the route is unmounted and to generate a new TOKEN to make a new request. See the Axios Doc to more details (https://github.com/axios/axios).

Route.tsx file

import React, { useEffect } from 'react';
import { Route, RouteProps, useLocation } from 'react-router-dom';
import API from 'src/services/service';

const CustomRoute = (props: RouteProps) => {
  const location = useLocation();

  // Detect Route Change
  useEffect(() => {
    handleRouteChange();

    return () => {
      handleRouteComponentUnmount();
    };
  }, [location?.pathname]);

  function handleRouteChange() {
    // ...
  }

  function handleRouteComponentUnmount() {
    API.finishPendingRequests('RouteChange');
  }

  return <Route {...props} />;
};

export default CustomRoute;

Service.ts file

import { Response } from 'src/models/request';
import axios, {AxiosInstance, AxiosResponse } from 'axios';

const ORIGIN_URL = 'https://myserver.com'
const BASE_URL = ORIGIN_URL + '/api';
let CANCEL_TOKEN_SOURCE = axios.CancelToken.source();
    
function generateNewCancelTokenSource() {
  CANCEL_TOKEN_SOURCE = axios.CancelToken.source();
}

export const axiosInstance: AxiosInstance = axios.create({
  baseURL: BASE_URL,
});

const API = {
  get<DataResponseType = any>(
    endpoint: string,
  ): Promise<AxiosResponse<Response<DataResponseType>>> {
    return axiosInstance.get<Response<DataResponseType>>(endpoint, {
      cancelToken: CANCEL_TOKEN_SOURCE.token,
    });
  },

  // ...Another Functions

  finishPendingRequests(cancellationReason: string) {
    CANCEL_TOKEN_SOURCE.cancel(cancellationReason);
    generateNewCancelTokenSource();
  },
};

export default API;
Related