React Dashboard登出后卡在加载页问题排查与修复请求
Dashboard登出后卡在空白页面的问题解决
点击登出按钮后,页面内容消失但停留在加载状态,必须手动刷新才会跳转到登录页,问题出在以下两点:
- token不是响应式状态:组件初始化时从
localStorage取的token是静态值,登出删除token后,useEffect的依赖没有变化,不会重新执行跳转逻辑。 - 登出后组件返回空白:登出时
setUser(null),此时isLoading已经是false,组件进入if (!user) return null分支,页面变成空白,没有触发跳转。
解决方案一:将token转为React状态(推荐)
把token改成useState,这样登出后token更新会自动触发useEffect重新执行,检查到token为空就跳转登录页:
import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; import axios from 'axios'; const Dashboard = () => { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); // 把token转为响应式状态 const [token, setToken] = useState(localStorage.getItem('token')); const navigate = useNavigate(); useEffect(() => { if (!token) { navigate('/login', { replace: true }); setIsLoading(false); // 避免一直显示加载 return; } const fetchUserData = async () => { try { const response = await axios.get('http://localhost:3001/api/auth/dashboard', { headers: { Authorization: `Bearer ${token}`, }, }); setUser(response.data); } catch (error) { if (error.response?.status === 401) { toast.error('请登录后访问控制台'); navigate('/login', { replace: true }); } else { toast.error('获取用户数据失败'); } } finally { setIsLoading(false); } }; fetchUserData(); }, [navigate, token]); const handleLogout = () => { localStorage.removeItem('token'); setToken(null); // 更新token状态 toast.success('登出成功!'); navigate('/login', { replace: true }); }; if (isLoading) { return ( <div className="flex justify-center items-center min-h-screen"> <p>加载中...</p> </div> ); } // 如果没有用户数据且已加载完成,直接跳转 if (!user) { navigate('/login', { replace: true }); return ( <div className="flex justify-center items-center min-h-screen"> <p>正在跳转...</p> </div> ); } return ( {/* 原Dashboard渲染内容不变 */} <div className="min-h-screen bg-background text-foreground p-6"> {/* Header Section */} <div className="flex justify-between items-center mb-6"> <div> <h1 className="text-4xl font-bold">Welcome, {user.name}</h1> <p className="text-muted-foreground">Rank: #{user.rank} | Problems Solved: {user.problemsSolved}</p> </div> <div className="flex items-center"> <img src={user.avatar || 'https://via.placeholder.com/50'} alt="User Avatar" className="w-16 h-16 rounded-full border border-border mr-4" /> <Button onClick={handleLogout} className="bg-destructive hover:bg-destructive-400 text-white"> Logout </Button> </div> </div> {/* Progress and Stats */} <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {/* Progress Overview */} <div className="bg-card rounded-lg shadow-md p-4"> <h2 className="text-xl font-semibold mb-4">Progress</h2> <div className="h-6 bg-muted rounded-full overflow-hidden"> <div className="h-full bg-primary" style={{ width: `${(user.problemsSolved / 100) * 100}%` }}></div> </div> <p className="text-sm text-muted-foreground mt-2">{user.problemsSolved}% Problems Solved</p> </div> {/* Quick Links */} <div className="bg-card rounded-lg shadow-md p-4"> <h2 className="text-xl font-semibold mb-4">Quick Links</h2> <ul className="space-y-2"> <li> <a href="/problems" className="text-primary hover:underline"> Solve Problems </a> </li> <li> <a href="/contests" className="text-primary hover:underline"> Join Contests </a> </li> <li> <a href="/settings" className="text-primary hover:underline"> Account Settings </a> </li> </ul> </div> {/* Statistics */} <div className="bg-card rounded-lg shadow-md p-4"> <h2 className="text-xl font-semibold mb-4">Statistics</h2> <p>Total Submissions: {user.totalSubmissions}</p> <p>Accepted: {user.accepted}</p> <p>Accuracy: {user.accuracy}%</p> </div> </div> {/* Recent Activity */} <div className="mt-6 bg-card rounded-lg shadow-md p-4"> <h2 className="text-xl font-semibold mb-4">Recent Activity</h2> <ul className="divide-y divide-border"> {user.recentActivity.map((activity, index) => ( <li className="py-2" key={index}> <p>{activity.problemTitle} - {activity.status}</p> <p className="text-sm text-muted-foreground">{activity.timestamp}</p> </li> ))} </ul> </div> </div> ); }; export default Dashboard;
解决方案二:直接在登出逻辑中处理状态
如果不想改动token的定义方式,可以在登出时先设置加载状态,同时避免返回空白:
const handleLogout = () => { localStorage.removeItem('token'); setIsLoading(true); // 先显示加载状态 toast.success('登出成功!'); navigate('/login', { replace: true }); }; // 修改无用户数据时的逻辑,不要返回null if (!user && !isLoading) { navigate('/login', { replace: true }); return ( <div className="flex justify-center items-center min-h-screen"> <p>正在跳转...</p> </div> ); }
总结
方案一更推荐,因为将token转为响应式状态后,能自动同步localStorage的变化,不仅解决登出问题,token过期等场景也能自动触发跳转逻辑,更符合React的状态管理规范。
内容的提问来源于stack exchange,提问作者Emperor
相关产品推荐
相关产品推荐

