Skip to content

Instantly share code, notes, and snippets.

@alexeychikk
Created December 27, 2017 11:27
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexeychikk/2078366d8fc975922294ae6210648a0f to your computer and use it in GitHub Desktop.
Save alexeychikk/2078366d8fc975922294ae6210648a0f to your computer and use it in GitHub Desktop.
Cancellation middleware for redux-axios-middleware
import {
CANCEL_ACTION_REQUESTS,
CANCEL_ALL_ACTION_REQUESTS
} from './constants';
export function cancelActionRequest(actionType) {
return { type: CANCEL_ACTION_REQUESTS, actionType };
}
export function cancelAllActionRequests() {
return { type: CANCEL_ALL_ACTION_REQUESTS };
}
export const CANCEL_ACTION_REQUESTS = 'CANCEL_ACTION_REQUESTS';
export const CANCEL_ALL_ACTION_REQUESTS = 'CANCEL_ALL_ACTION_REQUESTS';
export const CANCEL_DATA = { cancelled: true };
export { createCancellationMiddleware } from './middleware';
export * from './actions';
import axios from 'axios';
import {
CANCEL_ACTION_REQUESTS,
CANCEL_ALL_ACTION_REQUESTS,
CANCEL_DATA
} from './constants';
const cancelRequest = token => {
token.cancel(CANCEL_DATA);
token.onCancelCallback && token.onCancelCallback();
};
export function createCancellationMiddleware() {
return store => {
let tokensMap = {};
return next => action => {
const { actionType, payload, type, types } = action;
if (payload && payload.request && types && types.length && !type) {
const [actionTypeRequest] = types;
const source = axios.CancelToken.source();
source.onCancelCallback = payload.request.onCancel;
// Extend action so that redux-axios-middleware
// can understand that request is cancellable.
const cancelableAction = {
...action,
payload: {
...payload,
request: {
...payload.request,
cancelToken: source.token
}
}
};
// Store cancellation token so that we can cancel it
// later using actions.js in this folder.
const actionTokens = tokensMap[actionTypeRequest] || [];
actionTokens.push(source);
tokensMap[actionTypeRequest] = actionTokens;
return next(cancelableAction);
}
// Listen for cancel actions and cancel requests appropriately.
if (type === CANCEL_ACTION_REQUESTS) {
const actionTypes = Array.isArray(actionType)
? actionType
: [actionType];
actionTypes.forEach(actionType => {
const actionTokens = tokensMap[actionType];
if (actionTokens) {
actionTokens.forEach(cancelRequest);
tokensMap[actionType] = [];
}
});
} else if (type === CANCEL_ALL_ACTION_REQUESTS) {
Object.values(tokensMap).forEach(actionTokens =>
actionTokens.forEach(cancelRequest)
);
tokensMap = {};
}
return next(action);
};
};
}
@sunviwo
Copy link

sunviwo commented Jul 4, 2020

Hi, how do we use this in components ?

@alexeychikk
Copy link
Author

Hi, how do we use this in components ?

@sunviwo

const myAction = () => ({ type: constants.MY_ACTION });

const cancelMyAction = () => cancelActionRequest(constants.MY_ACTION);

Then in your component bind cancelMyAction through react-redux.

This code is not properly tested so be careful.

@nmorozov
Copy link

Fresh, working version with TypeScript

import { AnyAction, Middleware } from 'redux'
import axios, { CancelTokenSource } from 'axios'

interface LooseObject {
  [key: string]: CancelTokenSource[]
}

const CANCEL_ACTION_REQUESTS = 'CANCEL_ACTION_REQUESTS'
const CANCEL_ALL_ACTION_REQUESTS = 'CANCEL_ALL_ACTION_REQUESTS'

export const cancelAllActionRequests = (): AnyAction => {
  return { type: CANCEL_ALL_ACTION_REQUESTS }
}

export const cancelActionRequest = (actionType: string | string[]): AnyAction => {
  return { type: CANCEL_ACTION_REQUESTS, actionType }
}

const cancelRequest = (token: CancelTokenSource) => {
  token.cancel()
}

export const cancelAxiosRequestMiddleware: Middleware = () => {
  let tokensMap: LooseObject = {}
  return (next) => async (action) => {
    const { type, payload, actionType } = action

    if (payload && payload.request) {
      const source = axios.CancelToken.source()

      const cancelableAction = {
        ...action,
        payload: {
          ...payload,
          request: {
            ...payload.request,
            cancelToken: source.token,
          },
        },
      }

      const actionTokens = tokensMap[type] || []
      actionTokens.push(source)
      tokensMap[type] = actionTokens

      return next(cancelableAction)
    }

    if (type === CANCEL_ACTION_REQUESTS) {
      const actionTypes = Array.isArray(actionType) ? actionType : [actionType]

      actionTypes.forEach((at) => {
        const actionTokens = tokensMap[at]
        if (actionTokens) {
          actionTokens.forEach(cancelRequest)
          tokensMap[at] = []
        }
      })
    }

    if (type === CANCEL_ALL_ACTION_REQUESTS) {
      Object.values(tokensMap).forEach((actionTokens) => actionTokens.forEach(cancelRequest))
      tokensMap = {}
    }

    return next(action)
  }
}

Usage:

dispatch(cancelAllActionRequests())
// OR
dispatch(cancelActionRequest('YOUR_ACTION_TYPE'))
//OR
dispatch(cancelActionRequest(['YOUR_ACTION_TYPE', 'YOUR_ACTION_TYPE_N']))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment