TanStack Router中beforeLoad无法获取更新后Auth Context值的问题
问题描述
用户登录后将信息存入AuthContext,进入/addClient或/addProject页面时需校验用户类型。于是在_authenticated.tsx的beforeLoad函数中重新拉取用户信息并更新Context,但遇到以下问题:
- 在
_projectManager.tsx的beforeLoad函数中,context.auth的user为null,调用isProjectManager()无法正确校验权限(尽管用户实际类型是project-manager) _authenticated.tsx的组件内通过useAuth能获取到更新后的用户信息,但路由context中无法获取
项目路由结构
. 📂 routes/ ├── 📂 _authenticated/ │ ├── 📂 projectManager/ │ │ ├── addClient.tsx │ │ └── addProject.tsx │ └── _projectManager.tsx ├── 📂 _authentication/ │ ├── login.tsx │ └── signUp.tsx ├── _root.tsx ├── _authenticated.tsx ├── _authentication.tsx └── index.tsx
相关代码
AuthContext.tsx
import * as React from "react"; import Cookies from "js-cookie"; import { UserType } from "../types/userType"; export type AuthContext = { login: (token: string, user: UserType) => void; isLogged: () => boolean; getAuthToken: () => string | null; user: UserType | null; setUserDetails: (user: UserType) => void; isProjectManager: () => boolean; }; export const AuthContext = React.createContext<AuthContext | null>(null); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = React.useState<UserType>({} as UserType); function login(token: string, user: UserType) { Cookies.set("token", token, { expires: 7 }); setUser(user); } function getAuthToken() { const token = Cookies.get("token"); if (!token) { console.log("No token found"); return null; } return token; } function isLogged() { const token = getAuthToken(); return !!token; } function setUserDetails(user: UserType) { setUser(user); } function isProjectManager() { console.log(user?.userType); // 输出undefined,无法获取更新后的user值 return user?.userType === "project-manager"; } return ( <AuthContext.Provider value={{ user, setUserDetails, login, isLogged, getAuthToken, isProjectManager, }} > {children} </AuthContext.Provider> ); }
_authenticated.tsx
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; import Navbar from "../components/ui/Navbar"; import { fetchUserDetails } from "../services/apis"; import { useAuth } from "../hooks/useAuth"; export const Route = createFileRoute("/_authenticated")({ beforeLoad: async ({ context }) => { if (!context.auth.isLogged()) { throw redirect({ to: "/login" }); } const data = await fetchUserDetails(); return (context.auth.user = data.user); }, component: AuthenticatedLayout, }); function AuthenticatedLayout() { const auth = useAuth(); console.log(auth); // 能获取到更新后的用户信息 return ( <div> <Navbar /> <Outlet /> </div> ); }
_projectManager.tsx
import { createFileRoute, Outlet } from "@tanstack/react-router"; export const Route = createFileRoute("/_authenticated/_projectManager")({ beforeLoad: ({ context }) => { if (!context.auth.isProjectManager()) { console.log(context.auth.isProjectManager()); // 输出false throw new Error("You are not authorized to access this page"); } }, loader: ({ context }) => { console.log(context.auth.user); // 输出null或初始空对象 return context.auth.user; }, component: ProjectManagerLayout, errorComponent: () => <div>Unauthorized</div>, onError: (error) => { console.error(error); }, }); function ProjectManagerLayout() { return ( <div> <div>Hello /_authenticated/_projectManager!</div>, <Outlet /> </div> ); }
原因分析
- 路由context与React Context状态不同步:你在
_authenticated.tsx的beforeLoad中直接给context.auth.user赋值,这个修改仅作用于路由context对象,并未触发AuthProvider中useState的状态更新(即没调用setUser)。isProjectManager依赖的是React状态的闭包值,因此它读取的始终是初始的空user,而非路由context中更新后的值。 - 闭包陷阱:
isProjectManager函数在AuthProvider初始化时创建,捕获了当时的user状态。只要AuthProvider不重新渲染(未调用setUser),该函数就会一直使用旧的user值,无法感知路由context的修改。 - 初始状态不合理:
AuthProvider中user的初始值设为{} as UserType,导致user.userType默认是undefined,进一步干扰权限判断逻辑。
解决方案
方案1:同步路由context与React Context状态
修改_authenticated.tsx的beforeLoad逻辑,调用setUserDetails更新React状态,而非直接修改路由context的user:
// _authenticated.tsx export const Route = createFileRoute("/_authenticated")({ beforeLoad: async ({ context }) => { if (!context.auth.isLogged()) { throw redirect({ to: "/login" }); } const data = await fetchUserDetails(); // 调用setUserDetails触发React状态更新,同步AuthContext context.auth.setUserDetails(data.user); // 同步路由context的user值(可选,确保路由层能获取最新值) context.auth.user = data.user; }, component: AuthenticatedLayout, });
方案2:优化AuthProvider避免闭包陷阱
调整AuthProvider的初始状态,并确保isProjectManager每次渲染都捕获最新的user:
// AuthContext.tsx export function AuthProvider({ children }: { children: React.ReactNode }) { // 初始值设为null,逻辑更清晰 const [user, setUser] = React.useState<UserType | null>(null); // ...其他方法不变 // 每次渲染时重新创建该函数,确保捕获最新的user状态 function isProjectManager() { console.log(user?.userType); return user?.userType === "project-manager"; } return ( <AuthContext.Provider value={{ user, setUserDetails, login, isLogged, getAuthToken, isProjectManager, }} > {children} </AuthContext.Provider> ); }
方案3:在路由层直接校验权限(更可靠)
在_projectManager.tsx的beforeLoad中直接拉取用户信息并校验,避免依赖context的旧值:
// _projectManager.tsx import { fetchUserDetails } from "../services/apis"; export const Route = createFileRoute("/_authenticated/_projectManager")({ beforeLoad: async ({ context }) => { if (!context.auth.isLogged()) { throw redirect({ to: "/login" }); } // 直接拉取最新用户信息进行权限校验 const data = await fetchUserDetails(); if (data.user.userType !== "project-manager") { throw new Error("You are not authorized to access this page"); } // 同步更新状态 context.auth.setUserDetails(data.user); context.auth.user = data.user; }, // ...其他配置 });
内容的提问来源于stack exchange,提问作者Samiul Karim
相关产品推荐
相关产品推荐

