Jon Flynn
Jon Flynn

Reputation: 460

Emit multiple actions within catchError - Redux Observable

I have my auth function using rxjs epics:

export const authWithEmailPasswordEpic = action$ =>
action$.pipe(
    filter(authUser.match),
    switchMap(({payload}) =>
        defer(() =>
            from(firebaseEmailPasswordAuth(payload)).pipe(
                mergeMap(() => of(clearAlertState(), stopLoading())),
                catchError(err =>
                    of(clearState(), createAlert({
                        status: err.code,
                        alertType: RED_ALERT,
                        alertTitle: err.message
                    })),
                )
            )
        )
    )
);

If the users credentials are incorrect, I am dispatching an alert, but then I also want to remove the alert after 5 seconds or so. Therefore, I believe I'd need to emit two actions to achieve this, with the second one being:

of(toggleAlert()).pipe(delay(5000)))

How would I achieve this or is there a better way to do so?

Upvotes: 2

Views: 397

Answers (2)

Amer
Amer

Reputation: 6716

You can achieve that using delay(5000) after catchError, then mergeWith the of(toggleAlert()), like the following:

export const authWithEmailPasswordEpic = action$ =>
  action$.pipe(
    filter(authUser.match),
    switchMap(({ payload }) =>
      defer(() =>
        from(firebaseEmailPasswordAuth(payload)).pipe(
          mergeMap(() => of(clearAlertState(), stopLoading())),
          catchError(err =>
            of(
              clearState(),
              createAlert({
                status: err.code,
                alertType: RED_ALERT,
                alertTitle: err.message
              })
            )
          ),
          mergeWith(of(toggleAlert()).pipe(delay(5000)))
        )
      )
    )
  );

Upvotes: 1

olivarra1
olivarra1

Reputation: 3409

You're on the right track... For a simpler version I would use concat, I think it reads better:

export const authWithEmailPasswordEpic = (action$) =>
  action$.pipe(
    filter(authUser.match),
    switchMap(({ payload }) =>
      from(firebaseEmailPasswordAuth(payload)).pipe(
        mergeMap(() => of(clearAlertState(), stopLoading())),
        catchError((err) =>
          concat(
            [
              clearState(),
              createAlert({
                status: err.code,
                alertType: RED_ALERT,
                alertTitle: err.message,
              }),
            ],
            timer(5000).pipe(
              mapTo(toggleAlert())
            )
          )
        )
      )
    )
  );

With this pattern you can then make it fancier. Imagine you want to dispatch toggleAlert except if the user has made Action.CloseAlert. Then you can swap that timer.pipe for:

timer(5000).pipe(
  mapTo(toggleAlert()),
  takeUntil(action$.pipe(
    filter((action) => action.type === Action.CloseAlert)
  ))
);

Another note - I don't think that defer() is needed here. switchMap immediately subscribes to the result fo the function, so it shouldn't really matter. Add it back if you need it

Upvotes: 1

Related Questions