Implementing a Loading Middleware with Redux-Saga

· 1 min read · 202 Words · -Views -Comments

Some flows in our project take time. For a single API call, interceptors can toggle a loading spinner. But when an effect chains multiple requests and additional logic, API-level loading flickers on and off. We need loading control at the saga level.

Goal

When an action handled by a saga fires, show a loading mask. When the effect finishes, hide it.

Implementation

import { call, put } from 'redux-saga/effects';
import * as is from '@redux-saga/is';

const LOADING_BLACKLIST = [UserActionTypes.GET_USERS];

function isForkEffect(eff) {
  return is.effect(eff) && eff.type === 'FORK';
}

function loading(sagaFn) {
  return function* (action) {
    function* loadingWrapper() {
      yield put(loadingStatusAction(true));
      yield call(sagaFn, action);
      yield put(loadingStatusAction(false));
    }

    if (LOADING_BLACKLIST.includes(action.type)) {
      return yield call(sagaFn, action);
    } else {
      // @ts-ignore
      return yield call(loadingWrapper, action);
    }
  };
}

const effectMiddleware = (next) => (eff) => {
  if (isForkEffect(eff)) {
    eff.payload.args[1] = safe(eff.payload.args[1]);
    eff.payload.args[1] = loading(eff.payload.args[1]);
  }
  return next(eff);
};

export const sagaMiddleware = createSagaMiddleware({
  effectMiddlewares: [effectMiddleware],
  onError: sagaEffectUnhandled
});

Notes

  1. loading is a higher-order saga. It wraps the original saga function with “loading on/off” dispatches.
  2. Since saga functions are generators, the wrapper continues to return a generator.

Final Thoughts

Without middleware you’d repeat spinner toggling everywhere, violating DRY. This approach keeps the codebase clean.

Authors
Developer, digital product enthusiast, tinkerer, sharer, open source lover