Search code examples
reactjsfirebaseauthenticationreact-router-domobserver-pattern

How to handle Firebase onAuthStateChanged in a React app and route users accordingly?


I'm working on a React web app that is integrated with Firebase and I'm trying to authenticate my users. I have setup my route to display Home component if the user is authenticated and the Login page otherwise. But when my app first loads, it shows the Login page and it takes a second or two for it to recognize the user has actually been authenticated and then it switches to the Home page. It just seems messy and I'm guessing I'm not handling onAuthStateChanged quite well or fast enough. In my App component, I am subscribed to a ReactObserver that signals when auth state has changed. What can I do to avoid this weird behaviour?

My App.js:

import {BrowserRouter, Route} from "react-router-dom";
import Home from "./Home";
import Login from "./Login";
import {loggedIn, firebaseObserver} from "../firebase";
import {useEffect, useState} from "react";

export default function App() {
    const [authenticated, setAuthenticated] = useState(loggedIn())

    useEffect(() => {
        firebaseObserver.subscribe('authStateChanged', data => {
            setAuthenticated(data);
        });
        return () => { firebaseObserver.unsubscribe('authStateChanged'); }
    }, []);

   return <BrowserRouter>
             <Route exact path="/" render={() => (authenticated ? <Home/> : <Login/>)} />
          </BrowserRouter>;
}

My firebase.js:

import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import ReactObserver from 'react-event-observer';

const firebaseConfig = {
    apiKey: ...,
    authDomain: ...,
    projectId: ...,
    storageBucket: ...,
    messagingSenderId: ...,
    appId: ...
};
firebase.initializeApp(firebaseConfig);
export const auth = firebase.auth();
export const firestore = firebase.firestore();
export const firebaseObserver = ReactObserver();

auth.onAuthStateChanged(function(user) {
    firebaseObserver.publish("authStateChanged", loggedIn())
});

export function loggedIn() {
    return !!auth.currentUser;
}

Solution

  • To whom it may concern, adding a simple flag for when the authentication actually loaded solved the glitch for me. I also added protected routes (inspired by this article and I think I can continue on with my project nicely now.

    App.js:

    import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom";
    import Home from "./Home";
    import Login from "./Login";
    import {PrivateRoute} from "./PrivateRoute";
    import {loggedIn, firebaseObserver} from "../firebase";
    import {useEffect, useState} from "react";
    
    export default function App() {
        const [authenticated, setAuthenticated] = useState(loggedIn());
        const [isLoading, setIsLoading] = useState(true);
    
        useEffect(() => {
            firebaseObserver.subscribe('authStateChanged', data => {
                setAuthenticated(data);
                setIsLoading(false);
            });
            return () => { firebaseObserver.unsubscribe('authStateChanged'); }
        }, []);
    
        return isLoading ? <div/> :
            <BrowserRouter>
                <Switch>
                    <Route path="/" exact>
                        <Redirect to={authenticated ? "/home" : "/login"} />
                    </Route>
                    <PrivateRoute path="/home" exact
                                  component={Home}
                                  hasAccess={authenticated} />
                    <PrivateRoute path="/login" exact
                                  component={Login}
                                  hasAccess={!authenticated} />
                    <Route path="*">
                        <Redirect to={authenticated ? "/home" : "/login"} />
                    </Route>
                </Switch>
            </BrowserRouter>;
    }