0
votes

I have an epic where I am listening to an action stream, when an action of 'AUTH_STATUS_CHECKED' occurs switch over to a new observable authState(app.auth()). This new observable tells me if I am authenticated or not.

If the auth observable returns a user then dispatch 'SIGNED_IN' action with the user as the payload.

If a user does not exist then fore off a promise app.auth().signInWithPopup(googleAuthProvider) and when you get the response dispatch 'SIGNED_IN'.

If there are any errors, catch them and let me know in the console.

based on the logic above I have implemented the pipeline below

export const fetchAuthStatus = action$ =>
      action$.pipe(
        ofType('AUTH_STATUS_CHECKED'),
        switchMap(() =>
          authState(app.auth()).pipe(
            map(user =>
              user
                ? { type: 'SIGNED_IN', payload: user }
                : app
                    .auth()
                    .signInWithPopup(googleAuthProvider)
                    .then(user => ({ type: 'SIGNED_IN', payload: user }))
            ),
            catchError(error => console.log('problems signing in'))
          )
        )
      );

It's not working as expected. If there is no user the promise gets fired but it does not wait for the response and the dispatch never gets fired.

I'm clearly new to rxjs and I'm having trouble figuring this out. I've gone through the docs and tried using a tap() and mergeMap() but am getting the same error.

Any guidance would be appreciated.

2

2 Answers

1
votes

If you need to wait until some inner Observable/Promise completes you need to wrap it with eg. mergeMap or concatMap. Since both of these operators require that you return an Observable/Promise you'll have to wrap the action with of() to turn a plain object into an Observable:

import { of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

...
mergeMap(user => user
  ? of({ type: 'SIGNED_IN', payload: user })
  : app.auth().signInWithPopup(googleAuthProvider)...
)
1
votes

However your code can be optimized a bit and recommend do it in a better way, what you are missing here, is that map expect a plain object, while switchMap expect a Promise or Observable object.

const signIn = (authProvider) => app.auth().signInWithPopup(googleAuthProvider)
    .then(user => ({ type: 'SIGNED_IN', payload: user }));

export const fetchAuthStatus = action$ =>
    action$.pipe(
        ofType('AUTH_STATUS_CHECKED'),
        switchMap(() => authState(app.auth())),
        switchMap(user => user // <-- switchMap handle Promises or Observables
            ? of({ type: 'SIGNED_IN', payload: user }) // <-- returning an observable
            : signIn(googleAuthProvider) // <-- returning a promise
        ),
        catchError(error => console.log('problems signing in'))
    );

Notes:

  • Don't nest pipe operator, try to use one pipe as much as possible
  • Extract complicated method outside the Rx stream
  • use the of operator to convert plain object to Observable