2

Following this react-firestore-tutorial and the GitHub code. I wonder if the following is correct way to use the onAuthStateChanged or if I have understod this incorrect I'm just confused if this is the right way.

CodeSandBox fully connect with a test-account with apikey to Firebase!! so you can try it what I mean and I can learn this.

(NOTE: Firebase is blocking Codesandbox url even it's in Authorised domains, sorry about that but you can still see the code)

t {code: "auth/too-many-requests", message: "We have blocked all requests from this device due to unusual activity. Try again later.", a: null}a:

Note this is a Reactjs-Vanilla fully fledge advanced website using only;
React 16.6
React Router 5
Firebase 7

Here in the code the Firebase.js have this onAuthStateChanged and its called from two different components and also multiple times and what I understand one should only set it up once and then listen for it's callback. Calling it multiple times will that not create many listeners?

Can someone have a look at this code is this normal in Reactjs to handle onAuthStateChanged? (src\components\Firebase\firebase.js)

import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

class Firebase {
  constructor() {
    app.initializeApp(config);

   .......
  }

  .....

  onAuthUserListener = (next, fallback) =>
    this.auth.onAuthStateChanged(authUser => {
      if (authUser) {
        this.user(authUser.uid)
          .get()
          .then(snapshot => {
            const dbUser = snapshot.data();

            // default empty roles
            if (!dbUser.roles) {
              dbUser.roles = {};
            }

            // merge auth and db user
            authUser = {
              uid: authUser.uid,
              email: authUser.email,
              emailVerified: authUser.emailVerified,
              providerData: authUser.providerData,
              ...dbUser,
            };

            next(authUser);
          });
      } else {
        fallback();
      }
    });

  user = uid => this.db.doc(`users/${uid}`);

}

export default Firebase;

This two rect-higher-order Components:

First withAuthentication: (src\components\Session\withAuthentication.js)

import React from 'react';

import AuthUserContext from './context';
import { withFirebase } from '../Firebase';

const withAuthentication = Component => {
  class WithAuthentication extends React.Component {
    constructor(props) {
      super(props);

      this.state = {
        authUser: JSON.parse(localStorage.getItem('authUser')),
      };
    }

    componentDidMount() {
      this.listener = this.props.firebase.onAuthUserListener(
        authUser => {
          localStorage.setItem('authUser', JSON.stringify(authUser));
          this.setState({ authUser });
        },
        () => {
          localStorage.removeItem('authUser');
          this.setState({ authUser: null });
        },
      );
    }

    componentWillUnmount() {
      this.listener();
    }

    render() {
      return (
        <AuthUserContext.Provider value={this.state.authUser}>
          <Component {...this.props} />
        </AuthUserContext.Provider>
      );
    }
  }

  return withFirebase(WithAuthentication);
};

export default withAuthentication;

And withAuthorization: (src\components\Session\withAuthorization.js)

import React from 'react';
    import { withRouter } from 'react-router-dom';
    import { compose } from 'recompose';
    
    import AuthUserContext from './context';
    import { withFirebase } from '../Firebase';
    import * as ROUTES from '../../constants/routes';
    
    const withAuthorization = condition => Component => {
      class WithAuthorization extends React.Component {
        componentDidMount() {
          this.listener = this.props.firebase.onAuthUserListener(
            authUser => {
              if (!condition(authUser)) {
                this.props.history.push(ROUTES.SIGN_IN);
              }
            },
            () => this.props.history.push(ROUTES.SIGN_IN),
          );
        }
    
        componentWillUnmount() {
          this.listener();
        }
    
        render() {
          return (
            <AuthUserContext.Consumer>
              {authUser =>
                condition(authUser) ? <Component {...this.props} /> : null
              }
            </AuthUserContext.Consumer>
          );
        }
      }
    
      return compose(
        withRouter,
        withFirebase,
      )(WithAuthorization);
    };
    
    export default withAuthorization;
Badal Saibo
  • 2,499
  • 11
  • 23
Kid
  • 1,869
  • 3
  • 19
  • 48

1 Answers1

1

This is normal. onAuthStateChanged receives an observer function to which a user object is passed if sign-in is successful, else not.

Author has wrapped onAuthStateChanged with a higher order function – onAuthUserListener. The HOF receives two parameters as functions, next and fallback. These two parameters are the sole difference when creating HOC's withAuthentication and withAuthorization.

The former's next parameter is a function which stores user data on localStorage

localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });

while the latter's next parameter redirects to a new route based on condition.

if (!condition(authUser)) {
  this.props.history.push(ROUTES.SIGN_IN);
}

So, we are just passing different observer function based on different requirements. The component's we will be wrapping our HOC with will get their respective observer function on instantiation. The observer function are serving different functionality based on the auth state change event. Hence, to answer your question, it's completely valid.

Reference:

  1. https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
  2. https://reactjs.org/docs/higher-order-components.html
Badal Saibo
  • 2,499
  • 11
  • 23
  • Thanks I understand the flow but what I could not understand when in the firebase.js file there is only one `onAuthUserListener` listerner registration function with the `this.auth.onAuthStateChanged(authUser => {...` that hooks up both of the HOC's. I was thinking they lead to 2 listerners being registered. I think I get it there is only one listener and both HOC's are using this single one ok. In Android java the same is more easy to see how it works ok Thanks – Kid Oct 21 '20 at 11:58
  • 1
    Sorry for late reply. The reason we only have one `onAuthUserListener` is because it is a HOF. A HOF doesn't do anything by itself. It returns a function that does something. The something here is attaching an observer function to the `onAuthStateChanged` method, which is done by the two HOC. So you are right about 2 listeners being registered. One for `withAuthorization` and other `withAuthentication`. It's important to know that `onAuthStateChanged` isn't registered when it's defined in `onAuthUserListener`. It is only registered when `onAuthUserListener` HOF is called. – Badal Saibo Oct 22 '20 at 05:07