Lars Westby
Lars Westby

Reputation: 41

Wait for a second async action before continuing redux observable

I have two actions in a scenario:

INITIATE which triggers on a button click, and FETCH_SUCCESS which triggers when data has been stored in the state from an AJAX request called on page load.

The epic handling the INITIATE action requires the data from FETCH_SUCCESS being available in the state to work.

How can I write this epic so that it will 1) Do its logic as normal, as many times as it's been clicked after FETCH_SUCCESS has ran once, and 2) If clicked before FETCH_SUCCESS, wait for FETCH_SUCCESS to occur, and then continue to logic once the data is available / the success action has been ran.

There are at least a few easy approaches that solves this problem that I can think of, but don't want if this is possible to achieve with a single rxjx epic. For example: 1) Fail/retry flow where the epic simply checks if the data is available, and if not recursively calls itself with a small delay. 2) Make INITIATE set a flag in the state if FETCH_SUCCESS hasn't ran yet, and then listen to FETCH_SUCCESS and make it kick off INITIATE if this flag is set when it runs.

The only way I've come up with that don't include those two methods are writing it in two epics:

The version to handle FETCH_SUCCESS before INITIATE (happy path) looks like this:

export const initiateFirstEpic = action$ => action$
  .ofType(INITIATE)
  .skipUntil(Observable.of(FETCH_SUCCESS))
  .map(action => {
     return LogicAction();
  });

And the other to handle clicking too fast, making INITIATE happen before FETCH_SUCCESS:

export const successFirstEpic = action$ => action$
.ofType(INITIATE)
.switchMap(() =>
  action$.ofType(FETCH_SUCCESS)
    .take(1)
    .map(action => {
       return LogicAction();
    })
);

This doesn't seem to work, nor does it really look the way I want it to. Is there a good way to achieve this? I am quite new to RxJS and Redux Observable, so the answer might be obvious.

Upvotes: 2

Views: 1642

Answers (2)

deb0ch
deb0ch

Reputation: 1202

Here is a version based on @m1ch4ls but using the operator instead:

const initiateEpic = action$ =>
  action$
    .combineLatest(action$.ofType(FETCH_SUCCESS).take(1))
    .map(([initiateAction, fetch_success_action]) => {
      return LogicAction();
    });

Originally I didn't grasp that there was a static function Observable.combineLatest(obs1$, obs2$) and an operator obs1$.combineLatest(obs2$), hence my comment to @m1ch4ls answer.

Upvotes: 0

m1ch4ls
m1ch4ls

Reputation: 3435

I think what you are looking for is combineLatest.

export const initiateEpic = action$ => Observable
  .combineLatest(action$.ofType(INITIATE), action$.ofType(FETCH_SUCCESS).take(1))
  .map(([action, _]) => {
     return LogicAction();
  });

Optionally use take(1) on FETCH_SUCCESS action ofType observable to prevent producing the LoginAction every time you get FETCH_SUCCESS

Upvotes: 1

Related Questions