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

React TS登录页API调用后useNavigate跳转时而生效时而失效

登录后重定向失败问题排查与修复

问题描述

使用TypeScript + React开发的登录页面,API获取token功能正常,但登录成功后重定向至/路径的操作偶尔失败,表现为页面仅重新渲染,localStorage中已存在token,但路由跳转未生效。路由保护组件的else分支会执行,导致被重定向回登录页。

问题根源

核心问题出在认证状态的静态初始化:

  • 在App.tsx中,isAuthenticated是组件初始化时一次性计算的值,仅在App首次渲染时读取localStorage。
  • 登录成功后,localStorage写入token,但App组件没有重新渲染,因此传递给ProtectedRoute的isAuthenticated始终是初始的false。
  • 当登录后调用navigate('/'),ProtectedRoute判断未认证,会自动跳回/login,最终表现为页面重新渲染而非跳转至首页。

偶尔成功的情况是因为某些外部因素触发了App组件的重渲染(如其他状态更新),使得isAuthenticated被重新计算,但这属于不可控的偶然情况。

解决方案

1. 让认证状态响应localStorage变化

将App.tsx中的静态认证值改为状态变量,并监听localStorage的变化,确保认证状态实时更新:

import React, { useState, useEffect } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import ProtectedRoute from "./helper/restrictedRoute";
import Login from "./Login";
import Home from "./Home";

function App() {
  // 初始化时读取localStorage,之后通过监听更新
  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(
    localStorage.getItem("token") !== null
  );

  useEffect(() => {
    // 监听localStorage的token变化
    const handleStorageUpdate = () => {
      setIsAuthenticated(localStorage.getItem("token") !== null);
    };

    window.addEventListener("storage", handleStorageUpdate);
    // 组件卸载时移除监听
    return () => window.removeEventListener("storage", handleStorageUpdate);
  }, []);

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route 
          index 
          element={
            <ProtectedRoute 
              isAuthenticated={isAuthenticated} 
              authenticationPath="/login" 
              outlet={<Home />} 
            />
          } 
        />
      </Routes>
    </BrowserRouter>
  );
}
export default App;

2. 优化ProtectedRoute,增加实时检查

在路由保护组件中增加兜底的实时token检查,避免依赖父组件传递的状态延迟:

import { Navigate } from "react-router-dom";

export type ProtectedRouteProps = {
  isAuthenticated: boolean;
  authenticationPath: string;
  outlet: JSX.Element;
};

export default function ProtectedRoute({ isAuthenticated, authenticationPath, outlet }: ProtectedRouteProps) {
  // 实时检查localStorage中的token,作为兜底
  const isAuth = isAuthenticated || localStorage.getItem("token") !== null;
  
  if (isAuth) {
    return outlet;
  } else {
    // 使用replace替换历史记录,避免回退到登录页
    return <Navigate to={authenticationPath} replace />;
  }
}

3. 简化登录组件的重定向逻辑

去掉冗余的tokenProvided状态,直接在登录成功后调用navigate,减少状态更新带来的不确定性:

import { useNavigate } from "react-router-dom";
import { useState, useEffect } from "react";

function Login() {
  const [username, setUsername] = useState<string>("");
  const [password, setPassword] = useState<string>("");

  const navigate = useNavigate();
  
  useEffect(() => {
    document.title = "Login";
  }, []);

  const handleLogin = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      const loginData = { username, password };
      const responseData = await getLoginToken(loginData);
      localStorage.setItem("token", responseData.token);
      axios.defaults.headers.common["Authorization"] = `Bearer ${responseData.token}`;
      // 直接跳转并替换历史记录
      navigate('/', { replace: true });
    } catch (error) {
      createErrorToast("Invalid username or password.");
    }
  };

  return (
    <Page backgroundColorActive={false} navbarActive={false}>
      <LoginFormWrapper>
        {/* 表单内容 */}
        <LoginForm onSubmit={handleLogin}>
          {/* 输入框组件 */}
          <LoginFormButton type="submit">Login</LoginFormButton>
        </LoginForm>
      </LoginFormWrapper>
    </Page>
  );
}

export default Login;

修复逻辑说明

  • 通过useState和storage事件监听,确保App组件能实时感知localStorage中token的变化,更新认证状态。
  • ProtectedRoute的兜底检查避免了父组件状态更新延迟导致的判断错误。
  • 直接在登录成功后调用navigate,减少了中间状态的依赖,让跳转逻辑更直接可靠。

内容的提问来源于stack exchange,提问作者shutterpp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 10:35:13