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被清空,触发拦截器的登出逻辑。
修复步骤
- 删除清空token的useEffect
直接移除这段代码,它完全破坏了token持久化存储的目的:
useEffect(() => { localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); }, []);
- 添加页面刷新后的状态恢复逻辑
在应用根组件(如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]);
- 优化拦截器初始化逻辑
当前拦截器在组件每次渲染时都会重复创建和销毁,建议将拦截器移到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
相关产品推荐
相关产品推荐

