26

I am using Firebase to authenticate users for my application. I have created the SignIn and SignUp forms and can successfully create new users and sign in with stored users. However the issue comes with maintaining the user logged in state after a Reload.

The way I have seen it done in tutorials is to use a HOC like the following to check if the current user is logged in.

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

      this.state = {
        authUser: null,
      };
    }

    componentDidMount() {
      this.listener = this.props.firebase.auth.onAuthStateChanged(
        authUser => {
          authUser
            ? this.setState({ 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;

However I am looking to use the new React Hooks to remove the need for HOCs. I have already removed the withFirebase() HOC by using the React Context and useContext(FirebaseContext) to access a single instance of Firebase. Is there a way using the new hooks to mimic this withAuthentication HOC within components that I create?

I am using this tutorial

https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/

The section titled "Session Handling with Higher-Order Components" contains this part.

Thanks!

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Tristan Trainer
  • 2,770
  • 2
  • 17
  • 33
  • 4
    one option is to use the [react-firebase-hooks](https://firebaseopensource.com/projects/csfrequency/react-firebase-hooks/#auth) library, which has support for `onAuthStateChange` through its `useAuthState` hook – Jeff Mar 27 '19 at 00:01
  • Thanks Jeff, I did look into this, however I want to reduce the number of dependencies my project has as it will have minimal maintenance in future so I don't want to have to worry too much about breaking changes! – Tristan Trainer Mar 27 '19 at 09:09
  • @TristanTrainer - did you figure this out? I'm struggling with the same problem https://stackoverflow.com/questions/59977856/firebase-listener-with-react-hooks – Mel Feb 02 '20 at 00:16

1 Answers1

48

You can write a Custom Hook which registers an effect and returns the auth state

const useFirebaseAuthentication = (firebase) => {
    const [authUser, setAuthUser] = useState(null);

    useEffect(() =>{
       const unlisten = firebase.auth.onAuthStateChanged(
          authUser => {
            authUser
              ? setAuthUser(authUser)
              : setAuthUser(null);
          },
       );
       return () => {
           unlisten();
       }
    }, []);

    return authUser
}

export default useFirebaseAuthentication;

and in any Component you can use it like

const MyComponent = (props) => {
   const firebase = useContext(FirebaseContext);
   const authUser = useFirebaseAuthentication(firebase);
   
   return (...)
}

Index.jsx will have this code in it

ReactDOM.render( 
   <FirebaseProvider> 
      <App /> 
   </FirebaseProvider>, 
   document.getElementById('root')); 

This Firebase Provider is defined like this,

import Firebase from './firebase';

const FirebaseContext = createContext(); 
export const FirebaseProvider = (props) => ( 
   <FirebaseContext.Provider value={new Firebase()}> 
      {props.children} 
   </FirebaseContext.Provider> 
); 
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Beginner Question , am following the same tutorial . How did you write useContext(FirebaseContext) function .Can you please share the code if possible. Also how do you apply it if it is not similar to the provided in the tutorials . Thanks – 1nullpointer May 07 '19 at 11:35
  • 1
    @1nullpointer, useContext is a hook provided by react out of the box which you can use by importing like `import {useContext} from 'react';` – Shubham Khatri May 07 '19 at 11:47
  • am trying to follow the tutorials but write the code with hooks . If am providing the context at the App level component , where do i use this code . Thats where my confusion began . : const firebase = useContext(FirebaseContext); const authUser = useFirebaseAuthentication(firebase); Or should I wrap one into another .'Provide Firebase in React' Section in the tutorials specifically – 1nullpointer May 07 '19 at 12:00
  • 1
    @1nullpointer You will use useContext in components where you have `FirebaseContext.Consumer` in render of HOC. – Shubham Khatri May 07 '19 at 12:03
  • Ok, that means I need not wrap the whole render return stmt like that ? {firebase => { return
    I've access to Firebase and render something.
    ; }}
    – 1nullpointer May 07 '19 at 12:13
  • 1
    yes, you just need usecontext and that return you what the render prop of FirebaaseContext.Consumer returns in callback – Shubham Khatri May 07 '19 at 12:16
  • am getting Unhandled Rejection (TypeError): _Firebase__WEBPACK_IMPORTED_MODULE_6__.default is not a constructor @ Am doing import firebase from 'firebase/app' firebase.initializeApp(config) export default firebase – 1nullpointer May 09 '19 at 02:53
  • If I change it to then i get firebase.auth.onAuthStateChanged is not a function error – 1nullpointer May 09 '19 at 02:58
  • Never mind , got it working now by changing to firebase.auth().onAuthStateChanged() and – 1nullpointer May 09 '19 at 03:26
  • Question , can I merge below 2 lines so that i have a single line of code in all my consumer classes . Or does it vary based on context ? const firebase = useContext(FirebaseContext); const authUser = useFirebaseAuthentication(firebase); – 1nullpointer May 09 '19 at 03:32
  • 1
    You can craete a custom hook that calls these two hooks and returns the result, much like you did for `useFirebaseAuthentication` – Shubham Khatri May 09 '19 at 05:52
  • 3
    Thanks for this answer - it clarified a few things. FWIW, I ended up one-lining the useEffect, like so: `useEffect(() => firebase.auth.onAuthStateChanged(onChange), [])` – ConorLuddy Aug 08 '19 at 09:08
  • 1
    @ConorLuddy I think that might cause a memory leak because you are not closing the listener. – Faris Aug 08 '19 at 14:10
  • Does that mean when 'onAuthStateChanged' fires, MyComponent will rerender? (the value of authUser would update) – Faris Aug 10 '19 at 19:07
  • @Faris - my understanding is that useEffect will run whatever method it returns, which is the un-listen from onAuthStateChanged. Open to correction though... – ConorLuddy Aug 12 '19 at 11:47
  • 1
    @ConorLuddy yes, I think that is the case. Thank you for your reply. – Faris Aug 12 '19 at 18:30
  • @ShubhamKhatri - I'm stuck with my efforts to implement your solution - do you have an advice for how to get past these config issues? https://stackoverflow.com/questions/59977856/firebase-listener-with-react-hooks Thank you – Mel Feb 02 '20 at 00:11
  • 1
    @ShubhamKhatri Why are you not using empty brackets as dependency? You don't want to setup a listener again if state changes? Setting a listener just once is enough. – Badmaash Aug 29 '21 at 12:26
  • 1
    @Badmaash Thank you for pointing it out. I missed it in my answer – Shubham Khatri Aug 29 '21 at 17:31
  • thank you!!! really appreciate your answer – metamorph_online Jan 04 '22 at 16:02