Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux-saga firebase onAuthStateChanged eventChannel

How to handle firebase auth state observer in redux saga?

firebase.auth().onAuthStateChanged((user) => {

});

I want to run APP_START saga when my app starts which will run firebase.auth().onAuthStateChanged observer and will run other sagas depending on the callback.

As I understand eventChannel is right way to do it. But I don't understand how to make it work with firebase.auth().onAuthStateChanged.

Can someone show how to put firebase.auth().onAuthStateChanged in to eventChannel?

like image 875
rendom Avatar asked Aug 03 '18 12:08

rendom


4 Answers

You can use eventChannel. Here is an example code:

function getAuthChannel() {
  if (!this.authChannel) {
    this.authChannel = eventChannel(emit => {
      const unsubscribe = firebase.auth().onAuthStateChanged(user => emit({ user }));
      return unsubscribe;
    });
  }
  return this.authChannel;
}

function* watchForFirebaseAuth() {
  ...
  // This is where you wait for a callback from firebase
  const channel = yield call(getAuthChannel);
  const result = yield take(channel);
  // result is what you pass to the emit function. In this case, it's an object like { user: { name: 'xyz' } }
  ...
}

When you are done, you can close the channel using this.authChannel.close().

like image 111
vahissan Avatar answered Oct 13 '22 14:10

vahissan


Create your own function onAuthStateChanged() that will return a Promise

function onAuthStateChanged() {
  return new Promise((resolve, reject) => {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        resolve(user);
      } else {
        reject(new Error('Ops!'));
      }
    });
  });
}

Then use call method to get the user synchronously

const user = yield call(onAuthStateChanged);
like image 35
Andrei Zgîrvaci Avatar answered Oct 13 '22 15:10

Andrei Zgîrvaci


This could be handled in the Saga such as the following for Redux Saga Firebase:

// Redux Saga: Firebase Auth Channel
export function* firebaseAuthChannelSaga() {
  try {
    // Auth Channel (Events Emit On Login And Logout)
    const authChannel = yield call(reduxSagaFirebase.auth.channel);

    while (true) {
      const { user } = yield take(authChannel);

      // Check If User Exists
      if (user) {
        // Redux: Login Success
        yield put(loginSuccess(user));
      }
      else {
        // Redux: Logout Success
        yield put(logoutSuccess());
      }
    }
  }
  catch (error) {
    console.log(error);
  }
};
like image 1
jefelewis Avatar answered Oct 13 '22 15:10

jefelewis


here is how you would run the onAuthStateChanged observable using redux-saga features (mainly eventChannel)

import { eventChannel } from "redux-saga";
import { take, call } from "redux-saga/effects";

const authStateChannel = function () {
  return eventChannel((emit) => {
    const unsubscribe = firebase.auth().onAuthStateChanged(
      (doc) => emit({ doc }),
      (error) => emit({ error })
    );

    return unsubscribe;
  });
};

export const onAuthStateChanged = function* () {
  const channel = yield call(authStateChannel);

  while (true) {
    const { doc, error } = yield take(channel);
    if (error) {
      // handle error
    } else {
      if (doc) {
        // user has signed in, use `doc.toJSON()` to check
      } else {
        // user has signed out
      }
    }
  }
};

please note that other solutions that don't utilize channel sagas are not optimal for redux-saga, because turning an observable into a promise is not a valid solution in this case since you would need to call the promise each time you anticipate a change in authentication state (like for example: taking every USER_SIGNED_IN action and calling the "promisified" observable), which will negate the whole purpose of an observable

like image 1
Waddah Avatar answered Oct 13 '22 15:10

Waddah