2

First of all many props for this awesome library! I am still struggling with an use case though. I would like to call another epic first and wait till it completes before continuing the current epic. Let's say I have a form where an user can change things. Before loading other data into the form, I want to save the current changes first. Any thoughts on this?

robinvdvleuten
  • 1,462
  • 1
  • 16
  • 18

2 Answers2

6

Sounds like you want: if one epic would like to kick off another epics work and wait for it to complete before proceeding.

One approach is (using pseudo names), when receiving the initial action LOAD_OTHER_DATA you immediately start listening for a single SAVE_FORM_FULFILLED, which signals that the form has been saved (we'll kick it off in a bit). When received, we mergeMap (or switchMap, doesn't matter in this case) that into our call to load the other data loadOtherDataSomehow() and do the usual business. Finally, the trick to kick off the actual saving of the form we're waiting for is adding a startWith() at the end of that entire chain--which will emit and dispatch the action to actually save the form.

const saveFormEpic = (action$, store) =>
  action$
    .ofType('SAVE_FORM')
    .switchMap(() =>
      saveFormSomeHow(store.getState().formDataSomewhere)
        .map(details => ({
          type: 'SAVE_FORM_FULFILLED'
        }))
    );

const loadOtherDataEpic = action$ =>
  action$
    .ofType('LOAD_OTHER_DATA')
    .switchMap(() =>
      action$.ofType('SAVE_FORM_FULFILLED')
        .take(1) // don't listen forever! IMPORTANT!
        .mergeMap(() =>
          loadOtherDataSomeHow()
            .map(otherData => ({
              type: 'LOAD_OTHER_DATA_FULFILLED',
              payload: otherData
            }))
        )
        .startWith({
          type: 'SAVE_FORM'
        })
    );

You didn't mention how your loading and saving works, so this is just pseudo code that you'll need to amend for your use case.

jayphelps
  • 15,276
  • 3
  • 41
  • 54
  • wow thanks for the thorough answer! Can I have one small additional question on top of this? The saving is optional as in "when the user did not saved it by himself I would like the epic to do it for the him". So I looked at the Observer.if() method and looks like that will fit, but I can't figure out to do it optionally in the "loadOtherDataEpc". – robinvdvleuten Feb 17 '17 at 09:31
  • Just use a simple if/else conditional – jayphelps Feb 18 '17 at 19:07
  • Exactly what I was looking for! Thanks again for the thorough answer! – robinvdvleuten Feb 19 '17 at 10:00
  • @robinvdvleuten you're welcome! please accept this answer when you get a chance :) – jayphelps Feb 19 '17 at 22:10
1

@jayphelps, I just wanted to say thank you for the all questions you have answered, they have really helped me solve many of my problems.

I was trying to formulate my question and found that you had already answered it here and managed to use your sample to fix my problem.

My example has/had 2 expics that I want/ed to call after each other:

const requestNameEpic = action$ =>
  action$.ofType("REQUEST_NAME")
    .switchMap(({id}) =>
      Observable.of(`name returned by 'ajax' call, using id: ${id}`) // Ajax simulation
        .delay(1000)
        .map(name => ({type: "LOAD_NAME", name}))
    );

const requestAgeEpic = action$ =>
  action$.ofType("REQUEST_AGE")
    .switchMap(({name}) =>
      Observable.of(`age returned by 'ajax' call, using name: [${name}]`)
        .delay(1000)
        .map(age => ({type: "LOAD_AGE", age}))
    );

The way I managed to solve it was using:

const requestDetailsEpic = action$ =>
  action$.ofType("LOAD_NAME")
    .map(({name}) => ({type: "REQUEST_AGE", name}));

So dispatching REQUEST_NAME would also dispatch REQUEST_AGE, but then I would never be able to only dispatch LOAD_NAME.

I refactored your sample to get:

const requestDetailsEpic = action$ =>
  action$.ofType("REQUEST_DETAILS")
    .switchMap(({id}) =>
      action$.ofType("LOAD_NAME")
        .take(1) // don't listen forever! IMPORTANT!
        .map(({name}) => ({type: "REQUEST_AGE", name}))
        .startWith({type: "REQUEST_NAME", id})
  );

Thank you again for all the great work you do on redux-observable and all the community support.

Eli
  • 85
  • 3