Search code examples
reactjstypescriptredux-saga

Make takeEvery conditional on action.meta in redux sagas?


I have a redux-saga that I only want to fire for certain action.meta values:

function* handleUpdateSomeStuff(action): Generator {
  if (action.meta === "Special_Meta") {
    // run some code
  }
}

function* myConditionalSaga(): Generator {
  yield takeEvery(
    [ActionTypes.UPDATE_SOME_STUFF],
    handleUpdateSomeStuff
  );
}

function* itemSaga(): Generator {
  yield all([
    fork(myConditionalSaga),
    // ... more sagas
  ]);
}

Currently, I check within handleUpdateSomeStuff whether or not action.meta meets a certain condition. But I feel like the code should never even get that far. However, I don't see how the action argument might be available in myConditionalSaga to check it there. I am thinking something like this:

function* myConditionalSaga(action): Generator {
  if (action.meta === "Special_Meta") {
    yield takeEvery(
      [ActionTypes.UPDATE_SOME_STUFF],
      handleUpdateSomeStuff
    );
  }
}

The above block is more meta code than anything else, as trying to pass an argument to myConditionalSaga gives a TS error Argument of type '(action: any) => Generator<unknown, any, unknown>' is not assignable to parameter of type '{ context: unknown; fn: (this: unknown, ...args: any[]) => any; }'. inside the fork statement.

Is there a way to prevent handleUpdateSomeStuff from being called based on the action.meta of the action? I did indeed read Redux-saga takeLatest conditionally, as well as the docs on takeEvery, but I am struggling to undertsand how that may be applied to my scenario to get the desired result.

Opinions welcome in comments: Should I not even worry about it? Just let the function call reach myConditionalSaga and handle the if there?


Solution

  • take and its variants support a function as its first argument instead of an action type (or array of action types).

    So you should be able to do something like this:

    function* handleUpdateSomeStuff(action): Generator {
      // this runs only for when meta is "Special_Meta"
    }
    
    function* myConditionalSaga(): Generator {
      yield takeEvery(action => {
        return (
          action.type === ActionTypes.UPDATE_SOME_STUFF &&
          action.meta === "Special_Meta"
        );
      }, handleUpdateSomeStuff);
    }
    
    

    It might seem bit "wordy" like this, but if you need to limit the handlers based on the meta param more often, you can of course create some utility helper, e.g.:

    const typeAndMeta = (type, meta) => action => action.type === type && action.meta === meta
    yield takeEvery(typeAndMeta(ActionTypes.UPDATE_SOME_STUFF, 'Special_Meta'), saga);
    

    or even

    const takeEveryByMeta = (type, meta, saga, ...args) => {
        return takeEvery(action => action.type === type && action.meta === meta, saga, ...args);
    }
    yield takeEveryByMeta(ActionTypes.UPDATE_SOME_STUFF, 'Special_Meta', saga);
    

    You can easily extend these helpers to support array of types/meta values as well.