React-Router登录后无法跳转Dashboard,需手动刷新问题
问题:登录后自动跳转受保护路由失败,需手动刷新才能访问
我在实现受保护路由功能时遇到问题:登录成功后收到提示,调用navigate('/dashboard')却跳回首页,必须手动刷新页面才能进入Dashboard。相关代码如下:
routes.jsx
import React, { useContext } from 'react'; import { UserContext } from './context/userContext'; import { BrowserRouter as Router, Route, Routes, Navigate, } from 'react-router-dom'; import Dashboard from './pages/dashboard'; import Landingpage from './pages/landingpage'; import Profile from './pages/profile'; import Login from './pages/auth/login'; import Register from './pages/auth/register'; import Friends from './pages/friends'; import Notifications from './pages/notifications'; import { Loader } from 'lucide-react'; function MyRoutes() { const { user, loading } = useContext(UserContext); if (loading) { return <Loader />; } return ( <Router> <Routes> {user ? ( <> <Route path='/dashboard' element={<Dashboard />} /> <Route path='/profile' element={<Profile />} /> <Route path='/friends' element={<Friends />} /> <Route path='/notifications' element={<Notifications />} /> <Route path='*' element={<Navigate to='/dashboard' />} /> </> ) : ( <> <Route path='/' element={<Landingpage />} /> <Route path='/login' element={<Login />} /> <Route path='/register' element={<Register />} /> <Route path='*' element={<Navigate to='/login' />} /> </> )} </Routes> </Router> ); } export default MyRoutes;
userContext.jsx
import React, { createContext, useState, useEffect } from 'react'; import axios from 'axios'; export const UserContext = createContext(); export const UserContextProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); axios .get('/profile') .then((response) => { if (response.data.success) { setUser(response.data.user); } else { setUser(null); } }) .catch(() => { setUser(null); }) .finally(() => { setLoading(false); }); }, []); return ( <UserContext.Provider value={{ user, setUser, loading, setLoading }}> {children} </UserContext.Provider> ); };
authController.jsx(后端路由/profile)
const getProfile = async (req, res) => { logger.info(texts.INFO.ATTEMPTING_PROFILE_RETRIEVAL); const { token } = req.cookies; if (token) { jwt.verify( token, 'idsfu&ASUDIhiedUioGYUYFHIUGTygbhbhY3427HS', {}, async (err, decoded) => { if (err) { logger.warn(texts.WARNINGS.INVALID_TOKEN); return res.json({ error: texts.WARNINGS.INVALID_TOKEN }); } try { const user = await User.findById(decoded.id).select( '-password -goals' ); if (!user) { logger.warn(texts.WARNINGS.USER_NOT_FOUND); return res .status(404) .json({ error: texts.WARNINGS.USER_NOT_FOUND }); } logger.info(texts.SUCCESS.USER_LOGGED_IN(user.username)); res.json({ success: true, user }); } catch (error) { logger.error( texts.ERRORS.ERROR('user profile retrieval', error) ); res.status(500).json({ error: texts.ERRORS.USER_PROFILE_RETRIEVAL, }); } } ); } else { logger.warn(texts.WARNINGS.NO_TOKEN_FOUND); res.json(texts.WARNINGS.NO_TOKEN_FOUND); } };
login.jsx(仅逻辑部分)
const [data, setData] = useState({ emailOrUsername: '', password: '', }); const { setUser } = useContext(UserContext); const navigate = useNavigate(); const { addToast } = useToast(); const loginUser = async (event) => { event.preventDefault(); const { emailOrUsername, password } = data; try { const { data } = await axios.post('/login', { emailOrUsername, password, }); if (data.error) { addToast(data.error, 'error'); } else { setUser(data.user); setData({}); navigate('/dashboard'); addToast('Login erfolgreich!', 'success'); } } catch (error) { addToast( 'Login fehlgeschlagen. Bitte versuchen Sie es erneut. ' + error, 'error' ); } };
问题原因
核心问题出在路由组件的渲染逻辑和状态更新时机:
MyRoutes组件仅在初始化时读取一次user状态,登录后setUser更新状态时,组件并未重新渲染,导致路由规则仍沿用未登录时的配置,跳转/dashboard会被重定向到首页。- 当前通过
user值一次性切换整个路由列表的方式,无法响应状态更新后的权限变化。
解决方案
1. 重构路由组件,封装受保护路由
不要通过user值切换整个路由组,而是为每个需要保护的路由单独做权限校验:
修改routes.jsx:
import React, { useContext } from 'react'; import { UserContext } from './context/userContext'; import { BrowserRouter as Router, Route, Routes, Navigate, } from 'react-router-dom'; import Dashboard from './pages/dashboard'; import Landingpage from './pages/landingpage'; import Profile from './pages/profile'; import Login from './pages/auth/login'; import Register from './pages/auth/register'; import Friends from './pages/friends'; import Notifications from './pages/notifications'; import { Loader } from 'lucide-react'; // 封装受保护路由组件 const ProtectedRoute = ({ children }) => { const { user, loading } = useContext(UserContext); if (loading) { return <Loader />; } return user ? children : <Navigate to="/login" />; }; // 封装未登录可访问的路由组件 const PublicRoute = ({ children }) => { const { user, loading } = useContext(UserContext); if (loading) { return <Loader />; } return user ? <Navigate to="/dashboard" /> : children; }; function MyRoutes() { return ( <Router> <Routes> {/* 公开路由 */} <Route path='/' element={ <PublicRoute> <Landingpage /> </PublicRoute> } /> <Route path='/login' element={ <PublicRoute> <Login /> </PublicRoute> } /> <Route path='/register' element={ <PublicRoute> <Register /> </PublicRoute> } /> {/* 受保护路由 */} <Route path='/dashboard' element={ <ProtectedRoute> <Dashboard /> </ProtectedRoute> } /> <Route path='/profile' element={ <ProtectedRoute> <Profile /> </ProtectedRoute> } /> <Route path='/friends' element={ <ProtectedRoute> <Friends /> </ProtectedRoute> } /> <Route path='/notifications' element={ <ProtectedRoute> <Notifications /> </ProtectedRoute> } /> {/* 兜底路由 */} <Route path='*' element={ <ProtectedRoute> <Navigate to='/dashboard' /> </ProtectedRoute> } /> </Routes> </Router> ); } export default MyRoutes;
2. 优化登录跳转逻辑,监听状态变化
在login.jsx中添加状态监听,确保登录状态更新后再执行跳转:
const [data, setData] = useState({ emailOrUsername: '', password: '', }); const { user, setUser } = useContext(UserContext); const navigate = useNavigate(); const { addToast } = useToast(); // 监听user状态,登录成功后自动跳转 useEffect(() => { if (user) { navigate('/dashboard'); } }, [user, navigate]); const loginUser = async (event) => { event.preventDefault(); const { emailOrUsername, password } = data; try { const { data } = await axios.post('/login', { emailOrUsername, password, }); if (data.error) { addToast(data.error, 'error'); } else { setUser(data.user); setData({}); addToast('Login erfolgreich!', 'success'); // 移除手动navigate,交给useEffect处理 } } catch (error) { addToast( 'Login fehlgeschlagen. Bitte versuchen Sie es erneut. ' + error, 'error' ); } };
3. 修复UserContext的后端响应处理(可选)
后端无token时返回的是文本而非带success的对象,需要兼容处理:
修改userContext.jsx的useEffect:
useEffect(() => { setLoading(true); axios .get('/profile') .then((response) => { // 兼容后端返回的非JSON格式响应 if (typeof response.data === 'object' && response.data.success) { setUser(response.data.user); } else { setUser(null); } }) .catch(() => { setUser(null); }) .finally(() => { setLoading(false); }); }, []);
为什么这样修改?
- 封装
ProtectedRoute后,每个路由都会实时读取最新的user状态,状态更新时自动重新校验权限,避免了一次性渲染路由组的局限性。 - 通过
useEffect监听user变化,确保登录状态完全更新后再跳转,解决了状态更新延迟导致的路由校验失败问题。
内容的提问来源于stack exchange,提问作者Luke
相关产品推荐
相关产品推荐

