T
TanStack•7mo ago
extended-salmon

implement supabase auth into router context

i want to implement the supabase auth into my router context, but when i try to call the context auth in beforeLoad layout, it's not updated as in my AuthProvider component. when i change the code in AuthProvider, it will infinite loop. I've try this repo to implement in my code, but it's still not show the context auth in beforeLoad layout. can you guys know what's the problem??
5 Replies
continuing-cyan
continuing-cyan•7mo ago
can you provide a complete minimal example?
extended-salmon
extended-salmonOP•7mo ago
wait
// router-provider.tsx
import {
createRouter,
RouterProvider as RouterProviderBase,
} from "@tanstack/react-router";
import { routeTree } from "@/routeTree.gen";
import { CustomMetadataProps } from "./metadata-provider";
import { useAuth } from "./auth-provider";

const router = createRouter({
routeTree,
defaultPreload: "intent",
context: { auth: undefined! },
});

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
interface StaticDataRouteOption extends CustomMetadataProps {}
}

export default function RouterProvider() {
const auth = useAuth();

return <RouterProviderBase router={router} context={{ auth }} />;
}
// router-provider.tsx
import {
createRouter,
RouterProvider as RouterProviderBase,
} from "@tanstack/react-router";
import { routeTree } from "@/routeTree.gen";
import { CustomMetadataProps } from "./metadata-provider";
import { useAuth } from "./auth-provider";

const router = createRouter({
routeTree,
defaultPreload: "intent",
context: { auth: undefined! },
});

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
interface StaticDataRouteOption extends CustomMetadataProps {}
}

export default function RouterProvider() {
const auth = useAuth();

return <RouterProviderBase router={router} context={{ auth }} />;
}
// auth-provider.tsx
import React from "react";
import { supabaseClient } from "@/lib/supabase-client";
import { AuthError, Session, User } from "@supabase/supabase-js";

export interface AuthContextType {
client: typeof supabaseClient;
user: User | undefined;
session: Session | null;
isAuthenticated: boolean;
login: (username: string, password: string) => Promise<AuthReturn>;
logout: () => Promise<AuthReturn>;
}

const AuthContext = React.createContext<AuthContextType | undefined>(undefined);

export function useAuth() {
const context = React.useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}

return context;
}

interface AuthProviderProps {
children: React.ReactNode;
}

export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = React.useState<User | undefined>(undefined);
const [session, setSession] = React.useState<Session | null>(null);

const login = React.useCallback(
async (username: string, password: string) => {
...
},
[],
);

const logout = React.useCallback(async () => {
...
}, []);

React.useEffect(() => {
supabaseClient.auth.getSession().then(({ data }) => {
setSession(data?.session);
setUser(data?.session?.user);
});

const { data } = supabaseClient.auth.onAuthStateChange(
(_event, newSession) => {
setSession(newSession);
setUser(newSession?.user ?? undefined);
},
);

return () => data?.subscription.unsubscribe();
}, []);

const isAuthenticated = session ? true : false;

console.log("isAuthenticated", isAuthenticated); // This log showed my session
console.log("session", session); // This log showed my session

return (
<AuthContext.Provider value={{ client: supabaseClient, user, session, isAuthenticated, login }}>
{children}
</AuthContext.Provider>
);
}
// auth-provider.tsx
import React from "react";
import { supabaseClient } from "@/lib/supabase-client";
import { AuthError, Session, User } from "@supabase/supabase-js";

export interface AuthContextType {
client: typeof supabaseClient;
user: User | undefined;
session: Session | null;
isAuthenticated: boolean;
login: (username: string, password: string) => Promise<AuthReturn>;
logout: () => Promise<AuthReturn>;
}

const AuthContext = React.createContext<AuthContextType | undefined>(undefined);

export function useAuth() {
const context = React.useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}

return context;
}

interface AuthProviderProps {
children: React.ReactNode;
}

export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = React.useState<User | undefined>(undefined);
const [session, setSession] = React.useState<Session | null>(null);

const login = React.useCallback(
async (username: string, password: string) => {
...
},
[],
);

const logout = React.useCallback(async () => {
...
}, []);

React.useEffect(() => {
supabaseClient.auth.getSession().then(({ data }) => {
setSession(data?.session);
setUser(data?.session?.user);
});

const { data } = supabaseClient.auth.onAuthStateChange(
(_event, newSession) => {
setSession(newSession);
setUser(newSession?.user ?? undefined);
},
);

return () => data?.subscription.unsubscribe();
}, []);

const isAuthenticated = session ? true : false;

console.log("isAuthenticated", isAuthenticated); // This log showed my session
console.log("session", session); // This log showed my session

return (
<AuthContext.Provider value={{ client: supabaseClient, user, session, isAuthenticated, login }}>
{children}
</AuthContext.Provider>
);
}
// routes/_auth.tsx
import AuthLayout from "@/components/layout/auth-layout";
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_auth")({
component: AuthLayoutComponent,
beforeLoad: ({ context, location }) => {
console.log("isAuthenticated auth layout", context.auth.isAuthenticated); // This log NOT showed my session
console.log("context auth", context.auth); // This log NOT showed my session
if (context.auth.isAuthenticated) {
throw redirect({
to: "/dashboard",
search: { redirect: location.pathname },
});
}
},
});

function AuthLayoutComponent() {
return (
<AuthLayout>
<Outlet />
</AuthLayout>
);
}
// routes/_auth.tsx
import AuthLayout from "@/components/layout/auth-layout";
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_auth")({
component: AuthLayoutComponent,
beforeLoad: ({ context, location }) => {
console.log("isAuthenticated auth layout", context.auth.isAuthenticated); // This log NOT showed my session
console.log("context auth", context.auth); // This log NOT showed my session
if (context.auth.isAuthenticated) {
throw redirect({
to: "/dashboard",
search: { redirect: location.pathname },
});
}
},
});

function AuthLayoutComponent() {
return (
<AuthLayout>
<Outlet />
</AuthLayout>
);
}
extended-salmon
extended-salmonOP•7mo ago
the infinite loop issue that i have is more or less like this discussion https://github.com/TanStack/router/discussions/1238
GitHub
Getting out of memory error when navigating to authenticated route ...
I have a Vite React app which is using Supabase and Google OAuth for authentication. I have an AuthContext to keep track of authentication state. It has the following type: export interface AuthCon...
extended-salmon
extended-salmonOP•7mo ago
anyone able to help? 🥲 I think putting auth in the beforeLoad context will not work. because the beforeLoad() context will be executed once at the first time of access. while in the auth provider, the state will update every time the user has logged in or not. so I think I will use the traditional way by calling the useAuth hooks on the layout component instead of conditioning in the beforeLoad() context. maybe in the next update I hope, beforeLoad() can update the context every time there is a change in the state (for example in AuthProvider)
harsh-harlequin
harsh-harlequin•7mo ago
Last year, I worked with a user to get a working example repo with Supabase Auth working with Router https://github.com/dave-hawkins/tanstack-router-supabase-auth You'll want to take a look at - main.tsx - routes/_auth.tsx - routes/public/auth-callback.tsx`
GitHub
GitHub - dave-hawkins/tanstack-router-supabase-auth: Tanstack route...
Tanstack router + supabase auth starter repo. Contribute to dave-hawkins/tanstack-router-supabase-auth development by creating an account on GitHub.

Did you find this page helpful?