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
相关产品推荐
相关产品推荐

