redux-observable

How to dispatch two actions in one epic, which could be in the same or in another reducer


I have two ducks (ui and backend) with epics in them. I need to trigger two actions after finishing backend operations One of these actions reside in the backend duck, the other in the ui duck.

I started with the background action and things worked as expected. Adding the second action leads me to issues, as I can reach the action (console logs correctly), but not the reducer (no log)

The challenge I'm trying to solve is:

My code looks similar to this:

the backendDuck's epic:

fetchFooEpic : (action$, store) =>
  action$.pipe(
    operators.filter(action => action.type === types.LOAD),
    
    // start added section for second call
    operators.switchMap(action => {
      const response = operators.from(fetchSomeUrl(action))
      .pipe(
          operators.of(uiDuck.actions.fetchUserFulfilled(response.props)),
      ),
      operators.catchError(err => {
        console.error('Error happened!', err.message)
        return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
      })
      
      return response
    }),
    // start added section for second call

    // original first call
    operators.map(a => ({ type: types.ENDACTION,  payload: a.payload })),

    operators.catchError(err => {
      console.error('Error happened!', err.message)
      return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
    })
  )

the uiDuck:

export actions={
...
fetchUserFulfilled: (value) => {
  console.log('hello from action')
  return ({ type: types.FETCHUSERFULFILLED, payload: value })
},
...
}

...
export default function reducer(state = initialState, action) {
  switch (action.type) {
    case types.FETCHUSERFULFILLED:
      console.log('hello from reducer')
      return {
        ...state,
        user: action.payload,
      }
    ...
  

Solution

  • Turns out I was combining the two calls in the wrong way. For being able to pipe along, the piped observable needs to return an observable again.
    When mapping to another redux-action, it seems to me that it doesn't return an observable (?) thus, the call needs to happen for all desired redux-actions at the same location (eg with concat)

    For the sake of completeness I strive to explain all parts of the code in comments

    
    import * as operators from 'rxjs'
    
    fetchFooEpic : (action$, store) =>
    action$.pipe(
      operators.filter(action => action.type === types.LOAD),                       // Filter
      operators.switchMap(action =>                                                 // restart inner actions on each call
        operators.from(fetchSomeUrl(action))                                            // creating observable from result
        .pipe(                                                                      // starting new flow on observable (self)
          //operators.tap(a => console.log('Now running fetchfooepic 2', a)),       // dedicated location for sideeffects
          operators.switchMap(                                                      // restart inner actions on each call
            (response) => operators.concat(                                         // Kicking off several actions sequentially (merge() would do that in parallel)
            operators.of(uiDuck.actions.fetchUserFulfilled(response)),                   // addressing the redux action in other reducer
            operators.of(({                                                         // addressing the redux action via the type in this duck (ENDACTION is controlled by epics only, no action exists for it)
              type: types.ENDACTION,  
              payload: response
            }})),
          )),
          operators.catchError(err =>{
            console.error('Shit happens!', err.message)                             // errorhandling
            return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
          })
        )
      )
    ),
    

    Generally the functions are documented with some (more or less understandable) examples in https://rxjs.dev/api/index/function/