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

ReactJS:Local Storage存Token延迟致首次登录无法跳转至仪表盘

登录认证流程中的两个核心问题及解决方案

问题1:首次登录时LocalStorage存Token后仍被重定向回登录页

问题流程

  • 调用Login API获取带Token的响应
  • 将Token存入Local Storage
  • 跳转至受保护路由
  • 受保护组件读取Local Storage校验Token
  • 首次登录时组件无法读取到Token,判定未认证并重定向回登录页

原因分析

  1. 组件未监听Local Storage变化:原ProtectedRouter仅在挂载时读取一次Local Storage,后续Token写入后不会触发组件重新渲染,仍保持初始的未认证状态。
  2. 跳转时机可能过早:若跳转操作未等待Local Storage写入完成(虽Local Storage是同步API,但React组件渲染时机可能导致读取时未获取到最新值)。

解决方案

方案1:修改ProtectedRouter监听Local Storage变化

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

const ProtectedRoute = () => {
    const [isAuthenticated, setIsAuthenticated] = useState(
        () => JSON.parse(localStorage.getItem("token")) ?? false
    );

    useEffect(() => {
        // 监听Local Storage的token变化
        const handleStorageUpdate = () => {
            setIsAuthenticated(JSON.parse(localStorage.getItem("token")) ?? false);
        };

        window.addEventListener("storage", handleStorageUpdate);
        return () => window.removeEventListener("storage", handleStorageUpdate);
    }, []);

    return isAuthenticated ? <Outlet /> : <Navigate replace to="/login" />;
};

export default ProtectedRoute;

方案2:确保跳转在Token写入后执行

修改Login.jsx,在完成Local Storage写入后再执行跳转:

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

const login = ({email, password}) => {
    const navigate = useNavigate();
    axios.post(`http://localhost:7000/api/users/login`, { email, password }).then(response => {
        const { token, loggedInUser } = response.data;
        localStorage.setItem("loggedInUser", JSON.stringify(loggedInUser));
        localStorage.setItem("token", JSON.stringify(token));
        // 写入完成后再跳转
        navigate("/");
    }).catch(err => {
        console.error(err.message.toString());
    });
};

优化Router.jsx写法(符合React Router v6规范)

<Route element={<Layout/>}>
    <Route element={<ProtectedRoute />}>
        <Route path="/" element={<Dashboard/>}/>
    </Route>
</Route>

问题2:Token存在State中导致多标签页登出不同步

问题描述

将Token存储在组件State中时,单个标签页执行登出操作后,其他标签页的State不会同步更新,需手动逐个登出。

解决方案:结合Local Storage与全局Context实现多标签页同步

通过全局Auth Context维护Token状态,同时监听Local Storage变化,实现多标签页状态同步。

1. 实现全局Auth Context

// hooks/useAuth.js
import { createContext, useContext, useState, useEffect } from "react";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
    // 从Local Storage初始化Token状态
    const [token, setToken] = useState(() => {
        return JSON.parse(localStorage.getItem("token")) ?? null;
    });

    const login = (newToken, userInfo) => {
        localStorage.setItem("token", JSON.stringify(newToken));
        localStorage.setItem("loggedInUser", JSON.stringify(userInfo));
        setToken(newToken);
    };

    const logout = () => {
        localStorage.removeItem("token");
        localStorage.removeItem("loggedInUser");
        setToken(null);
    };

    // 监听Local Storage变化,同步更新State
    useEffect(() => {
        const handleStorageChange = (e) => {
            if (e.key === "token") {
                setToken(JSON.parse(e.newValue) ?? null);
            }
        };

        window.addEventListener("storage", handleStorageChange);
        return () => window.removeEventListener("storage", handleStorageChange);
    }, []);

    return (
        <AuthContext.Provider value={{ token, login, logout }}>
            {children}
        </AuthContext.Provider>
    );
};

export const useAuth = () => useContext(AuthContext);

2. 修改Login组件使用全局Auth方法

import { useAuth } from "../hooks/useAuth";
import { useNavigate } from "react-router-dom";

const Login = () => {
    const { login } = useAuth();
    const navigate = useNavigate();

    const handleLogin = ({ email, password }) => {
        axios.post(`http://localhost:7000/api/users/login`, { email, password })
            .then(response => {
                const { token, loggedInUser } = response.data;
                login(token, loggedInUser);
                navigate("/");
            })
            .catch(err => {
                console.error(err.message.toString());
            });
    };

    // 其他组件逻辑...
};

3. 修改ProtectedRoute使用全局Auth状态

import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

const ProtectedRoute = () => {
    const { token } = useAuth();
    return token ? <Outlet /> : <Navigate replace to="/login" />;
};

这样,任意标签页执行登出操作时,Local Storage的Token会被移除,其他标签页的监听器会同步更新State,实现自动登出。


内容的提问来源于stack exchange,提问作者S Ahmed Naim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 02:36:18