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

React Dashboard登出后卡在加载页问题排查与修复请求

Dashboard登出后卡在空白页面的问题解决

点击登出按钮后,页面内容消失但停留在加载状态,必须手动刷新才会跳转到登录页,问题出在以下两点:

  1. token不是响应式状态:组件初始化时从localStorage取的token是静态值,登出删除token后,useEffect的依赖没有变化,不会重新执行跳转逻辑。
  2. 登出后组件返回空白:登出时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:12:08