New Sep 18, 2024

Improve my AuthGuard for NextJS with Firebase

Libraries, Frameworks, etc. All from Newest questions tagged reactjs - Stack Overflow View Improve my AuthGuard for NextJS with Firebase on stackoverflow.com

I am working with the T3 Stack and got stuck creating an AuthGuard. This AuthGuard essentially acts as a Page Manager that redirects the user to the appropriate page.

I have set up a working version, but I am seeing ways to reduce redirects, add loading screens, and minimize screen flashing.

The SessionContext calls the database to fetch user information, such as schemes and roles. SessionProvider is wrapped around AuthGuard

Given this, notFound() is displayed for a split second (in cases where page is not found), then the login is shown and then the redirected to Home or else login.

How can I improve this without using middleware.ts or other 3rd party libraries?

"use client";

import { PropsWithChildren, useContext, useEffect, useState } from "react"; import { SessionContext } from "./SessionContext"; import { usePathname, useRouter } from "next/navigation";

const PUBLIC_ROUTES = ['/login', '/signup'];

export const AuthGuard: React.FC<PropsWithChildren> = ({ children }) => {     const context = useContext(SessionContext);     const user = context?.user;     const loading = context?.loading;     const error = context?.error;     const pathname = usePathname();     const router = useRouter();     const [hasCheckedAuth, setHasCheckedAuth] = useState(false);

useEffect(() => {         if (!loading) {             if (!user && !PUBLIC_ROUTES.includes(pathname)) {                 router.replace('/login');             } else if (user && PUBLIC_ROUTES.includes(pathname)) {                 router.replace('/');             } else {                 setHasCheckedAuth(true);             }         }     }, [user, loading, pathname]);

if (loading || !hasCheckedAuth) {         return <LoadingSpinner />;     }

if (error) {         return <div>Error: {error.message}</div>;     }

return <>{children}</>; };

const LoadingSpinner: React.FC = () => (     <div className="flex justify-center items-center h-screen">         <div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>     </div> );


SessionContext.tsx

"use client";

import { createContext, PropsWithChildren, useCallback, useEffect, useMemo, useState } from "react"; import { SessionUser } from "~/types/User"; import { useFirebaseSession } from "./hooks/useFirebaseSession"; import { api } from "~/trpc/react";

interface SessionContextType { user: SessionUser | null; loading: boolean; error: Error | null; }

export const SessionContext = createContext<SessionContextType | undefined>(undefined);

export const SessionProvider: React.FC<PropsWithChildren> = ({ children }) => { const { user: firebaseUser, loading: firebaseLoading, error: firebaseError } = useFirebaseSession(); const [sessionUser, setSessionUser] = useState<SessionUser | null>(null);

const { data: dbUser, error: dbError, isLoading: dbLoading, refetch: refetchDbUser } = api.user.getSessionUser.useQuery( undefined, { enabled: !!firebaseUser,

} );

useEffect(() => { if(dbUser && firebaseUser) { setSessionUser({ ...firebaseUser, // Add Database User Data }) } else { setSessionUser(null); } }, [dbUser, firebaseUser]);

useEffect(() => { if(firebaseUser) { void refetchDbUser(); } else { setSessionUser(null); } }, [firebaseUser, refetchDbUser]);

const value = useMemo(() => { return { user: sessionUser, error: dbError instanceof Error ? dbError : firebaseError ?? null, loading: firebaseLoading ?? dbLoading } }, [sessionUser, dbError, firebaseError, firebaseLoading, dbLoading])

return ( <SessionContext.Provider value={value}>{children}</SessionContext.Provider > ) }

Scroll to top