Search code examples
reactjsreduxrxjs5redux-observable

In redux-observable - How can I dispatch an action "mid-stream" without returning the value of that action?


I have searched everywhere and cannot find an answer to the following...

In my addItemEpic I would like to dispatch a "loading" action before sending the ajax request - I DO NOT want the value of the action to be returned - I just want to DO the action then return the ajax response. This can be achieved easily with store.dispatch() as below:

export const addItemsEpic = (action$, store) =>
  action$.ofType(ADD_ITEM)
    .do(() => store.dispatch({
        type: 'layout/UPDATE_LAYOUT',
        payload: { loading: true } 
    }))
    .switchMap(action => api.addItem(action.payload))
    .map(item => loadItem(item))
    .do(() => store.dispatch({
       type: 'layout/UPDATE_LAYOUT',
       payload: { loading: false } 
    }))
    .catch(error => Observable.of({
       type: 'AJAX_ERROR',
       payload: error,
       error: true
   }));

However store.dispatch() is deprecated and discouraged in redux-observable.

I have attempted to use almost every plausible operator and still I cannot dispatch the action without disrupting the return value in the next chained function. I thought something like te following should do it:

action$.ofType(ADD_ITEM)
   .concatMap(action => Observable.of(
      updateLayout({loading: true}),
      api.addItem(action.payload)).last())
   .map(item => loadItem(item))

But unfortunately 2 problems here:

  • no loading-action is dispatched (the value IS returned as an observable)
  • api request now returns a wrapped promise instead of a mappable value

I'd really appreciate some help or advice here as I can't find any way to get this working.

Thanks in advance


Solution

  • The second form is on the right path. The idea is to return an Observable that first emits the loading action, then after the service call, emits either the loaded action or error action, and finally emits the done loading action.

    const addItemEpic = action$ => action$
      .ofType(ADD_ITEM)
      .switchMap(action => { // or use mergeMap/concatMap depending on your needs
        const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
          .map(item => actions.loadItem(item))
          .catch(err => Observable.of(actions.ajaxError(err)));
    
        return Observable.concat(
          Observable.of(actions.updateLayout(true)),
          Observable.concat(apiCall$,
            Observable.of(actions.updateLayout(false))));
      });
    

    Observable.fromPromise() converts a Promise to an Observable