kmui2
kmui2

Reputation: 2517

How can I dispatch one action, wait 1 second, then dispatch two more actions in Redux Observable?

How would I do the following in a single epic

  1. Dispatch pauseGame()
  2. Wait 1 second
  3. Dispatch 2 actions

The following dispatches the last two actions but not pauseGame().

enter image description here

const moveEpic: RootEpic = (action$, state$) =>
  action$.pipe(
    filter(isActionOf(move)),
    map(() => pauseGame()),
    filter(() => state$.value.ruleRow.lastMoveSuccessful),
    delay(1000),
    switchMap(() => [
      removeBoardObject(
        state$.value.ruleRow.totalMoveHistory[state$.value.ruleRow.totalMoveHistory.length - 1]
          .dragged,
      ),
      resumeGame(),
    ]),
  );

Upvotes: 1

Views: 518

Answers (1)

Kevin Ghadyani
Kevin Ghadyani

Reputation: 7297

The reason pauseGame isn't dispatching is because you're not dispatching it. You're calling the action creator and then immediately changing your observable state to lastMoveSuccessful.

Instead, what you want is to split the pipeline and merge them back to one. I know this is confusing, but it's how Redux-Observable works at the moment. If you want a different way where you can dispatch at any point in time, checkout my article: https://dev.to/sawtaytoes/the-best-practice-anti-pattern-jj6

When the move type occurs, switch to a new observable. That observable is a merging of 2 new observables: one which immediately dispatches pauseGame, the other which checks if the last move was successful, and if so, waits a second and dispatches 2 other actions.

const moveEpic: RootEpic = (action$, state$) => (
  action$.pipe(
    filter(isActionOf(move)),
    switchMap(() => (
      merge(
        of(pauseGame()),
        of(state$.value.ruleRow.lastMoveSuccessful).pipe(
          filter(Boolean),
          delay(1000),
          concatMap(() => [
            removeBoardObject(
              state$.value.ruleRow.totalMoveHistory[
                state$.value.ruleRow.totalMoveHistory.length - 1
              ].dragged
            ),
            resumeGame(),
          ]),
        )
      )
    )),
  );
)

As a side note, I don't know why you've created your own isActionOf function, but normally you should be able to change that line to ofType(move) instead.

Evert Bouw provided a simpler suggestion utilizing startWith and endWith. You'll lose sequential ordering in your pipeline, but you don't have to split it:

const moveEpic: RootEpic = (action$, state$) => (
  action$.pipe(
    filter(isActionOf(move)),
    switchMap(() => (
        of(state$.value.ruleRow.lastMoveSuccessful).pipe(
          filter(Boolean),
          delay(1000),
          map(() => (
            removeBoardObject(
              state$.value.ruleRow.totalMoveHistory[
                state$.value.ruleRow.totalMoveHistory.length - 1
              ].dragged
          )),
          startWith(pauseGame()),
          endWith(resumeGame()),
        )
      )
    )),
  );
)

Keep in mind, if you needed to know the value of state$ in endWith, you'd want to use finalize instead. It takes a function instead of a value.

Upvotes: 2

Related Questions