adrianmcli
adrianmcli

Reputation: 1996

Dispatching a delayed second action with Redux-Observable while keeping the first one

I'm trying to create an epic that will take an action, and then dispatch two different actions, with the second one delayed by two seconds. After a bunch of attempts, this was the best I could do:

const succeedEpic = action$ =>
  action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
    .mapTo({ type: 'FETCH_REQUEST' })
    .merge(Observable.of({ type: 'FETCH_SUCCESS' }).delay(2000))

Unfortunately, it seems that:

Observable.of({ type: 'FETCH_SUCCESS' }).delay(2000)

Is run immediately upon my app being loaded (rather than when an event comes down the parent stream). I noticed this because the FETCH_SUCCESS action is received by the reducer exactly two seconds after my app is loaded. I even attached a console.log to confirm this:

const succeedEpic = action$ =>
  action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
    .mapTo({ type: 'FETCH_REQUEST' })
    .merge(Observable.of({ type: 'FETCH_SUCCESS' })
           .do(() => console.log('this has begun'))
           .delay(2000)
          )

"this has begun" is logged to the console the moment the app is started.

I suspect this has something to do with how Redux-Observable automatically subscribes for you.

The desired behaviour is that I will:

  1. Click a button that dispatches the FETCH_WILL_SUCCEED event.
  2. Immediately, a FETCH_REQUEST event is dispatched.
  3. Two seconds after that, a FETCH_SUCCESS event is dispatched.

Upvotes: 0

Views: 2350

Answers (2)

adrianmcli
adrianmcli

Reputation: 1996

It turns out I needed to wrap both of my events inside a mergeMap. Thanks to @dorus on the RxJS Gitter channel for this answer.

This is my working result:

const succeedEpic = action$ =>
  action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
    .mergeMapTo(Observable.of({ type: 'FETCH_REQUEST' })
      .concat(Observable.of({ type: 'FETCH_SUCCESS' })
        .delay(1000)))

merge should work as well in place of concat, but I thought concat makes better semantic sense.

Upvotes: 2

Balázs Édes
Balázs Édes

Reputation: 13807

There might be a more elegant solution, but why not just use 2 epics and combine them?

The first one dispatches the fetch request:

const onFetchWillSucceed = action$ => action$.ofType('FETCH_WILL_SUCCEED')
  .mapTo({ type: 'FETCH_REQUEST' })

The second one waits 2 secs and dispatches the success:

const onFetchRequest = action$ => action$.ofType('FETCH_REQUEST')
  .delay(2000)
  .mapTo({ type: 'FETCH_SUCCESS' })

And in the end they are just combined into 1 epic

const both = combineEpics(onFetchWillSucceed, onFetchRequest)

Upvotes: 0

Related Questions