0

I have React app which uses redux-observable with typescript. In this scenario, FetchAttribute Action gets triggered with a id and then make an ajax call. In certain case, I would want to cancel the ajax request if "FETCH_ATTRIBUTE_CANCEL" action was triggered with the same id as of "FetchAttributeAction" action.

action$.ofType(FETCH_ATTRIBUTE)
    .switchMap((request: FetchAttributeAction) => {

      return ajax.getJSON(`/api/fetch-attribute?id=${request.id}`)
        .flatMap((fetchUrl) => {
            // return new action
        })
        .takeUntil(action$.ofType(FETCH_ATTRIBUTE_CANCEL));
    });

interface FetchAttributeAction{
  id: number;
}

Problem: How do we cancel the execution based on action type + action data? In my case, it would FETCH_ATTRIBUTE_CANCEL and id.

ashokd
  • 393
  • 2
  • 11

2 Answers2

4

The key is to filter actions in the takeUntil notifier to only those which match the ID you care about.

action$.ofType(FETCH_ATTRIBUTE_CANCEL).filter(action => action.id === request.id)

So here's what it might look like:

Demo: https://stackblitz.com/edit/redux-observable-playground-xztkoo?file=fetchAttribute.js

const fetchAttributeEpic = action$ =>
  action$.ofType(FETCH_ATTRIBUTE)
    .mergeMap(request =>
      ajax.getJSON(`/api/fetch-attribute?id=${request.id}`)
        .map(response => fetchAttributeFulfilled(response))
        .takeUntil(
          action$.ofType(FETCH_ATTRIBUTE_CANCEL).filter(action => action.id === request.id)
        )
    );

You can also take a look at previous questions:


The OP also pointed out that they were using switchMap (as did I originally when I copied their code) which would have meant that the epic only ever had one getJSON at a time since switchMap will unsubscribe from previous inner Observables. So that also needed to be chained. Good catch!

jayphelps
  • 15,276
  • 3
  • 41
  • 54
  • Of course, there was a bug and its because of use of switchMap. I modified your code and its reproducible there too. https://stackblitz.com/edit/redux-observable-playground-v6dhlc { "actions": [ { "type": "@@redux/INIT" }, { "type": "FETCH_ATTRIBUTE", "id": "1" }, { "type": "FETCH_ATTRIBUTE", "id": "2" }, { "type": "FETCH_ATTRIBUTE", "id": "22" }, { "type": "FETCH_ATTRIBUTE_FULFILLED", "response": { "response": "/api/fetch-attribute?id=22" } } ] } – ashokd Jan 25 '18 at 22:34
  • Still works for me in that link. Did you maybe forget to save? But yes, you are right that if you use `switchMap` you'll have implicit cancellation--this epic will only ever have one pending getJSON. If you want concurrent you can use `mergeMap`. I didn't notice that in your OP lol nice catch!! – jayphelps Jan 25 '18 at 23:08
  • Thanks for the help. – ashokd Jan 26 '18 at 04:02
0

I think you should be able to make takeUntil selective for a certain action id with pluck and filter.

ex:

.takeUntil(action%.ofType(FETCH_ATTRIBUTE_CANCEL)
.pluck('id')
.filter((cancelActionID) => cancelActionID === fetchID))

The non-obvious part to me is how to get the current fetchID to run that comparison. I might consider try using do to store in a temporary variable

jdpigeon
  • 391
  • 4
  • 8
  • How is above solution different than below code? It didn't work for me. ```return ajax.getJSON('/api/fetch-attribute?id='+request.id) .flatMap((fetchUrl) => { // return new action }) .takeUntil(action$.ofType(FETCH_ATTRIBUTE_CANCEL).filter(action => action.payload === request.id)); });``` – ashokd Jan 25 '18 at 17:02
  • That's pretty much what I recommended to do. Are you sure you're getting the correct request.id inside that filter function? Have you tested whether firing that cancel action (through a button or something) is able to shut down the execution of that ajax call correctly? – jdpigeon Jan 25 '18 at 17:14
  • Yes, it's getting correct request.id and when cancel action is fired it shut down the execution. I have tested this using console.log statements – ashokd Jan 25 '18 at 17:55