reactjsfirebase-authenticationreact-router-domdataloader

How to keep user logged in on refresh of dashboard page using firebase onAuthStateChange in a react app and react router dom RouterProvider API


I am using react router dom RouterProvider which decouples fetching from rendering, from the official remix-run react router example of auth-router-provider, it was stated in the README.md that

we can no longer rely on React context and/or hooks to get our user authentication status. We need access to this information outside of the React tree so we can use it in our route loader and action functions. https://github.com/remix-run/react-router/tree/dev/examples/auth-router-provider

I created a standalone object outside of the React tree that manages my authentication state for login and logout, my object structure looks like

export const authProvider: AuthProvider = {
  user: null,

  async login(email: string, password: string) {
    return signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        authProvider.user = userCredential.user;
        return redirect("/dashboard");
      })
      .catch(() => {
        return toast.error("Password or Email invalid");
      });
  },

  async signout() {
    signOut(auth);
    authProvider.user = null;
  },
};

I import the standalone object my loader file and return authProvider.user in my dashboard loader

loader() {
  return { user: authProvider.user };
}

the user is access using the useRouteLoaderData in the dashboard component

const {user} = useRouteLoaderData("dashboard")

the user will be initially null and I can navigate else where if the user is not logged in, but when a user is logged in the user is no longer null and can view the dashboard page, but when i refresh the dashboard page after a user login the user always return null.

I can't seem to get right where i will use the onAuthStateChange from firebase because i was using it inside a useEffect earlier when i was managing user with react context.

I need assistnace on how to make the user persist on refresh of the dashboard page.


Solution

  • Update your authProvider object to include a function that can call Firebase's onAuthStateChanged function to check the current user's authentication status. Wrap the call in a Promise that resolves when the onAuthStateChanged succeeds, or rejects in the failure case.

    import {
      getAuth,
      onAuthStateChanged,
      signInWithEmailAndPassword,
      signOut,
    } from "firebase/auth";
    
    const auth = getAuth();
    
    interface AuthProvider {
      user: User | null;
      checkAuth(): Promise<void>;
      login(email: string, password: string): Promise<void>;
      signout(): Promise<void>;
    }
    
    export const authProvider: AuthProvider = {
      user: null,
    
      checkAuth: async () => {
        return new Promise((resolve, reject) => {
          onAuthStateChanged(
            auth,
            (user) => {
              authProvider.user = user; // User | null
              resolve();
            },
            reject
          );
        });
      },
      login: async (email: string, password: string) => {
        return signInWithEmailAndPassword(auth, email, password)
          .then((userCredential) => {
            authProvider.user = userCredential.user;
            return redirect("/dashboard");
          })
          .catch(() => {
            return toast.error("Password or Email invalid");
          });
      },
      signout: async () => {
        signOut(auth);
        authProvider.user = null;
      },
    };
    

    Call your authProvider instance's checkAuth function and wait for it to settle.

    loader = async () => {
      try {
        await authProvider.checkAuth();
        return {
          user: authProvider.user
        };
      } catch(error) {
        // handle/ignore error
      }
    }