skip to Main Content

I am building an app using Node.js v18 and React v18 and I have the following problem.

I am redirecting my user when he tries to go to login but is already logged in. The problem is that for a short second, the login page is rendered.

AuthContext.tsx

import { User, getAuth, onAuthStateChanged } from "firebase/auth";
import React, { useState, useEffect, createContext, PropsWithChildren} from 'react';

export const AuthContext = createContext<User | null>(null);

export const AuthProvider = (props : PropsWithChildren) => {
    const [user, setUser] = useState<User | null>(null);

    useEffect(() => {
        onAuthStateChanged(getAuth(), (currentUser) => {
            setUser(currentUser);
        })
    }, []);

    return <AuthContext.Provider value={user}>{props.children}</AuthContext.Provider>
}

App.tsx

import React, { useContext } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import LoginPage from "./authentication/LoginPage";
import RegisterPage from "./authentication/RegisterPage";
import { AuthContext } from "./context/AuthContext";
import PaymentPage from "./payment/PaymentPage";


export default function App(){
    const user = useContext(AuthContext);

    function redirect(){
        if(user !== null){
            return <Navigate to="/pay" />;
        }else{
            return <LoginPage />;
        } 
    }

    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={redirect()}/>
                <Route path="/register" element={<RegisterPage />}/>
                <Route path="/pay" element={<PaymentPage />}/>
            </Routes>
        </BrowserRouter>
        
    );
}

Any ideas on how to solve this problem ?

Thank you

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to @Frank van Puffelen I managed to implement a solution :

    AuthContext.tsx

    import { User, getAuth, onAuthStateChanged } from "firebase/auth";
    import React, { useState, useEffect, createContext, PropsWithChildren} from 'react';
    
    export const AuthContext = createContext<UserLogged>({user : null, loading: true});
    
    //Add an interface to store the loading state
    interface UserLogged {
        user : User | null ;
        loading : boolean; 
    }
    
    export const AuthProvider = (props : PropsWithChildren) => {
        const [user, setUser] = useState<User | null>(null);
        const [loading, setLoading] = useState(true);
    
        useEffect(() => {
            onAuthStateChanged(getAuth(), (currentUser) => {
                setUser(currentUser);
                //once the user has been set I now the loading is over
                setLoading(false);
            })
        }, []);
    
        return <AuthContext.Provider value={{user: user, loading: loading}}>{props.children}</AuthContext.Provider>
    }
    

    App.tsx

    import React, { useContext } from "react";
    import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
    import LoginPage from "./authentication/LoginPage";
    import RegisterPage from "./authentication/RegisterPage";
    import { AuthContext } from "./context/AuthContext";
    import PaymentPage from "./payment/PaymentPage";
    import { Audio } from 'react-loader-spinner'
    
    
    export default function App(){
        const userLogged = useContext(AuthContext);
    
        function redirect(){
            if(userLogged.loading){
                // add a loading indicator
                return <Audio />;
            }else{
                if(userLogged.user !== null){
                    return <Navigate to="/pay" />;
                }else{
                    return <LoginPage />;
                } 
            }
        }
    
        return (
            <BrowserRouter>
                <Routes>
                    <Route path="/" element={redirect()}/>
                    <Route path="/register" element={<RegisterPage />}/>
                    <Route path="/pay" element={<PaymentPage />}/>
                </Routes>
            </BrowserRouter>
            
        );
    }
    

  2. Firebase automatically restores the user credentials from the local storage of the device, but during this process is makes a call to the server to check if those credentials are still valid, for example if the account hasn’t been suspended. The onAuthStateChanged listener won’t fire its first event until this asynchronous call has completed, which may indeed take a moment. Your UI uses the lack of a current user as a sign to show the login UI, which causes that UI to show briefly.

    Typically you’ll want to show some loading indicator while the check is going on, until you get the first onAuthStateChanged event. This is the simplest solution as you now handle all three cases: the user is not logged in, the user is logged in, and when we don’t know yet whether the user is logged in.

    Alternatively, you can store a value in local storage to indicate that the user was logged in before, and based on that assume that they will be logged in successfully again as Michael Bleigh showed in this I/O talk. Note though that in this case there’s a change that your app thinks the user is logged in initially, but then turns out to be wrong – for example when the account was suspended – so you’ll have to handle this flow by then redirecting back to the log-in page.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search