You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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'
            );
        }
    };

问题原因

核心问题出在路由组件的渲染逻辑和状态更新时机:

  1. MyRoutes组件仅在初始化时读取一次user状态,登录后setUser更新状态时,组件并未重新渲染,导致路由规则仍沿用未登录时的配置,跳转/dashboard会被重定向到首页。
  2. 当前通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:10:55