React受保护路由刷新后重定向至根路径问题求助
React 路由刷新重定向问题
问题现象
登录后访问 /dashboard 等受保护路由,刷新页面时会被重定向至根路径 /;但刷新 /myaccount 这类非受保护路由时无此问题。
期望行为
刷新页面后保持当前路由状态,所有路由均不会被重定向至根路径 /。
已尝试操作
- 检查了 Context 中的
token和userType状态管理 - 确认登录后
fetchData函数能正确获取用户类型
相关代码
App.js
import React, { Suspense, lazy } from "react"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import Layout from "./components/layout/Layout"; import { CombinedProvider } from "./contexts/useContext"; import { ToastContainer } from "react-toastify"; import ProtectedRoute from "./utils/ProtectedRoute"; const SelectionPage = lazy(() => import("./pages/SelectionPage")); const SignupPage = lazy(() => import("./pages/Signup")); const LoginTanod = lazy(() => import("./pages/LoginTanod")); const LoginResident = lazy(() => import("./pages/ResidentLogin")); // Tanod routes const Dashboard = lazy(() => import("./components/users/tanods/Dashboard")); const Patrolmap = lazy(() => import("./components/users/tanods/Map")); const Equipments = lazy(() => import("./components/users/tanods/Equipment")); const Performance = lazy(() => import("./components/users/tanods/Performance")); const Schedule = lazy(() => import("./components/users/tanods/Schedule")); const Incidents = lazy(() => import("./components/users/tanods/Incidents")); const MyAccount = lazy(() => import("./components/users/tanods/MyAcc")); // Resident routes const ResidentRating = lazy(() => import("./components/users/residents/TanodPersonels")); const ResidentDashboard = lazy(() => import("./components/users/residents/Dashboard")); function App() { return ( <div className="flex-1 p-6 bg-background text-text"> <BrowserRouter> <CombinedProvider> <Suspense fallback={<div>Loading...</div>}> <ToastContainer /> <Routes> {/* Public Routes */} <Route path="/" element={<SelectionPage />} /> <Route path="/tanod-login" element={<LoginTanod />} /> <Route path="/resident-login" element={<LoginResident />} /> <Route path="/signup" element={<SignupPage />} /> <Route element={<Layout />}> <Route path="/myaccount" element={<MyAccount />}/> </Route> {/* Protected Routes for Tanod */} <Route element={<Layout />}> <Route path="/dashboard" element={<ProtectedRoute userTypeAllowed={["tanod"]}><Dashboard /></ProtectedRoute>} /> <Route path="/patrolmap" element={<ProtectedRoute userTypeAllowed={["tanod"]}><Patrolmap /></ProtectedRoute>} /> <Route path="/equipments" element={<ProtectedRoute userTypeAllowed={["tanod"]}><Equipments /></ProtectedRoute>} /> <Route path="/performance" element={<ProtectedRoute userTypeAllowed={["tanod"]}><Performance /></ProtectedRoute>} /> <Route path="/schedule" element={<ProtectedRoute userTypeAllowed={["tanod"]}><Schedule /></ProtectedRoute>} /> <Route path="/incidents" element={<ProtectedRoute userTypeAllowed={["tanod"]}><Incidents /></ProtectedRoute>} /> </Route> {/* Protected Routes for Residents */} <Route element={<Layout />}> <Route path="/resident-dashboard" element={<ProtectedRoute userTypeAllowed={["resident"]}><ResidentDashboard /></ProtectedRoute>} /> <Route path="/ratetanod" element={<ProtectedRoute userTypeAllowed={["resident"]}><ResidentRating /></ProtectedRoute>} /> </Route> </Routes> </Suspense> </CombinedProvider> </BrowserRouter> </div> ); } export default App;
ProtectedRoute.js
import React from "react"; import { Navigate } from "react-router-dom"; import { useCombinedContext } from "../contexts/useContext"; export default function ProtectedRoute({ userTypeAllowed, children }) { const { userType, token } = useCombinedContext(); if (!token || !userTypeAllowed.includes(userType)) { return <Navigate to="/" />; } return children; }
useContext.js
import React, { createContext, useState, useEffect, useContext, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; export const CombinedContext = createContext(); export const CombinedProvider = ({ children }) => { const [token, setToken] = useState(localStorage.getItem('token')); const [userType, setUserType] = useState(null); const navigate = useNavigate(); const fetchData = useCallback(async () => { // Fetch user data to get userType }, []); useEffect(() => { if (token) { fetchData(); } }, [token, fetchData]); const logout = () => { localStorage.removeItem('token'); setToken(null); setUserType(null); navigate('/'); }; return ( <CombinedContext.Provider value={{ token, userType, logout }}> {children} </CombinedContext.Provider> ); }; export const useCombinedContext = () => useContext(CombinedContext);
问题原因
刷新页面时,Context 初始化阶段 userType 初始值为 null,而 ProtectedRoute 会立即检查 userType 是否在允许列表中。此时 fetchData 还未完成异步请求,userType 仍为 null,触发重定向逻辑。
解决办法
1. 添加加载状态,避免过早重定向
在 CombinedProvider 中添加 isLoading 状态,标记用户数据是否加载完成:
修改 useContext.js:
import React, { createContext, useState, useEffect, useContext, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; export const CombinedContext = createContext(); export const CombinedProvider = ({ children }) => { const [token, setToken] = useState(localStorage.getItem('token')); const [userType, setUserType] = useState(null); const [isLoading, setIsLoading] = useState(true); // 添加加载状态 const navigate = useNavigate(); const fetchData = useCallback(async () => { setIsLoading(true); try { // 补充实际的接口请求逻辑 const response = await fetch('/api/user-profile', { headers: { Authorization: `Bearer ${token}` } }); const data = await response.json(); setUserType(data.userType); } catch (error) { toast.error('获取用户信息失败'); logout(); } finally { setIsLoading(false); } }, [token]); useEffect(() => { if (token) { fetchData(); } else { setIsLoading(false); } }, [token, fetchData]); const logout = () => { localStorage.removeItem('token'); setToken(null); setUserType(null); navigate('/'); }; return ( <CombinedContext.Provider value={{ token, userType, logout, isLoading }}> {children} </CombinedContext.Provider> ); }; export const useCombinedContext = () => useContext(CombinedContext);
2. 修改 ProtectedRoute,等待加载完成再判断
在 ProtectedRoute 中,当 isLoading 为 true 时,返回加载组件,避免立即重定向:
修改 ProtectedRoute.js:
import React from "react"; import { Navigate } from "react-router-dom"; import { useCombinedContext } from "../contexts/useContext"; export default function ProtectedRoute({ userTypeAllowed, children }) { const { userType, token, isLoading } = useCombinedContext(); if (isLoading) { return <div>Loading...</div>; // 或自定义加载组件 } if (!token || !userTypeAllowed.includes(userType)) { return <Navigate to="/" />; } return children; }
3. 优化:从 localStorage 直接读取 userType(可选)
如果登录时将 userType 也存入 localStorage,可以避免每次刷新都请求接口:
登录成功后:
localStorage.setItem('token', 'your-token'); localStorage.setItem('userType', 'tanod'); // 或 'resident'
修改 useContext.js 的初始化:
const [userType, setUserType] = useState(localStorage.getItem('userType'));
这样刷新时 userType 会直接从 localStorage 获取,无需等待异步请求完成,进一步优化体验。
内容的提问来源于stack exchange,提问作者Maka
相关产品推荐
相关产品推荐

