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

React页面刷新时LocalStorage中Token被清空问题求助

问题:React页面刷新后LocalStorage中的access token变为null导致频繁登出

我正在开发一个React应用,使用LocalStorage存储access token进行用户认证。但每次刷新页面时,access token都会变为null,导致我不断登出,找不到问题所在,恳请帮忙解决。

登录组件代码

const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();

// const from = location.state?.from?.pathname || "/admin/documents";

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("Đã xảy ra lỗi!");
const [emailMessage, setEmailMessage] = useState("");
const [passwordMessage, setPasswordMessage] = useState("");
const [status, setStatus] = useState(0);
const [isLoading, setIsLoading] = useState(false);

const errRef = useRef();

useEffect(() => {
    localStorage.removeItem("accessToken");
    localStorage.removeItem("refreshToken");
}, []);

useEffect(() => {
    setMessage("");
    setEmailMessage("");
    setPasswordMessage("");
}, [email, password]);

const validateEmail = () => {
    if (email === "" || email.trim() === "") {
        setEmailMessage("Email không được để trống");
    } else if (!emailRegrex.test(email)) {
        setEmailMessage("Email không hợp lệ");
    }
};

const validatePassword = () => {
    if (password === "" || password.trim() === "") {
        setPasswordMessage("Mật khẩu không được để trống");
    }
};

const handleSubmit = async (e) => {
    e.preventDefault();

    setIsLoading(true);

    validateEmail();
    validatePassword();

    if (!emailMessage && !passwordMessage) {
        try {
            const response = await axios.post(LOGIN_URL, JSON.stringify({ email, password }), {
                headers: { "Content-Type": "application/json" },
                withCredentials: true,
            });

            setIsLoading(false);

            if (response.data.status === 401) {
                setMessage("Email hoặc mật khẩu không đúng!");
                setStatus(-1);
                setTimeout(() => {
                    setStatus(0);
                }, 4000);
            } else if (response.data.status === 403) {
                setMessage("Tài khoản không có quyền truy cập!");
                setStatus(-1);
                setTimeout(() => {
                    setStatus(0);
                }, 4000);
            } else if (response.data.status === 400) {
                setMessage("Có lỗi xảy ra!");
                setStatus(-1);
                setTimeout(() => {
                    setStatus(0);
                }, 4000);
            } else {
                alert("JJJJJJJJ")
                setMessage("Đăng nhập thành công!");
                localStorage.setItem("accessToken", response.data.data.accessToken);
                localStorage.setItem("refreshToken", response.data.data.refreshToken);

                const config = {
                    headers: {
                        Authorization: "Bearer " + response.data.data.accessToken,
                    },
                };

                const res = await getProfile(config);
                const user = res.data;

                dispatch(loginAction.setUser(user));

                setStatus(1);
                setTimeout(() => {
                    navigate("/manager/home");
                    setStatus(0);
                }, 2000);
            }
        } catch (error) {
            setStatus(-1);
            setMessage("Đã xảy ra lỗi!");
            setTimeout(() => {
                setStatus(0);
            }, 4000);
        }
    }
};

Token附加拦截器代码

useEffect(() => {
    const requestInterceptor = privateAxios.interceptors.request.use(
        (config) => {
            const accessToken = localStorage.getItem("accessToken");

            if (!accessToken) navigate("/admin/login");
            else config.headers.Authorization = `Bearer ${accessToken}`;

            return config;
        },
        (error) => Promise.reject(error),
    );

    const responseInterceptor = privateAxios.interceptors.response.use(
        (response) => {
            if (response.data.status === 401) {
                if (response.data.message === "User unauthorized. Please log in first.") navigate("/admin/login");
            } else if (response.data.status === 403) navigate("/admin/login");

            return response;
        },
        (error) => {
            return Promise.reject(error);
        },
    );

    return () => {
        privateAxios.interceptors.request.eject(requestInterceptor);
        privateAxios.interceptors.response.eject(responseInterceptor);
    };
}, [navigate]);
return privateAxios;

问题分析与解决方案

核心问题

你在登录组件的空依赖useEffect中执行了localStorage.removeItem("accessToken")和localStorage.removeItem("refreshToken"),这个useEffect会在组件挂载时(包括页面刷新后组件重新渲染挂载)立即删除存储的token,直接导致刷新后token被清空,触发拦截器的登出逻辑。

修复步骤

  1. 删除清空token的useEffect
    直接移除这段代码,它完全破坏了token持久化存储的目的:
useEffect(() => {
    localStorage.removeItem("accessToken");
    localStorage.removeItem("refreshToken");
}, []);
  1. 添加页面刷新后的状态恢复逻辑
    在应用根组件(如App.js或AuthProvider组件)中添加逻辑,页面刷新时从LocalStorage读取已有的token,重新同步到Redux状态:
// 根组件示例
useEffect(() => {
    const accessToken = localStorage.getItem("accessToken");
    if (accessToken) {
        const config = { headers: { Authorization: `Bearer ${accessToken}` } };
        getProfile(config)
            .then(res => dispatch(loginAction.setUser(res.data)))
            .catch(() => {
                // token失效时清空存储并跳转登录页
                localStorage.removeItem("accessToken");
                localStorage.removeItem("refreshToken");
                navigate("/admin/login");
            });
    }
}, [dispatch, navigate]);
  1. 优化拦截器初始化逻辑
    当前拦截器在组件每次渲染时都会重复创建和销毁,建议将拦截器移到axios实例的全局初始化文件中,避免重复操作:
// api/privateAxios.js
import axios from "axios";
import { useNavigate } from "react-router-dom";

const privateAxios = axios.create({ baseURL: "你的API基础地址" });

// 请求拦截器(全局只初始化一次)
privateAxios.interceptors.request.use(
    (config) => {
        const accessToken = localStorage.getItem("accessToken");
        if (accessToken) {
            config.headers.Authorization = `Bearer ${accessToken}`;
        } else {
            window.location.href = "/admin/login";
        }
        return config;
    },
    (error) => Promise.reject(error)
);

// 响应拦截器(全局只初始化一次)
privateAxios.interceptors.response.use(
    (response) => {
        if ([401, 403].includes(response.data.status)) {
            localStorage.removeItem("accessToken");
            localStorage.removeItem("refreshToken");
            window.location.href = "/admin/login";
        }
        return response;
    },
    (error) => {
        if ([401, 403].includes(error.response?.status)) {
            localStorage.removeItem("accessToken");
            localStorage.removeItem("refreshToken");
            window.location.href = "/admin/login";
        }
        return Promise.reject(error);
    }
);

export default privateAxios;

内容的提问来源于stack exchange,提问作者Văn Thuận Nguyễn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 10:40:56