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

useEffect无法监听localStorage变更问题(关联react-query)

问题:调用useLogout登出后,Header组件的localStorage监听不生效

手动删除localStorage中的access字段时,Header组件里的useEffect能正常响应并更新isLogin状态;但通过ProfileBar组件调用useLogout钩子执行登出时,虽然onSuccess回调已触发,access和refresh字段也被移除,但Header的监听逻辑完全没反应。


原因

浏览器的storage事件有个关键特性:只有当同源的其他窗口/标签页修改localStorage时,当前页面的storage监听器才会触发。当在当前页面内部通过代码修改localStorage(比如useLogout的onSuccess里的操作),浏览器不会触发当前页面的storage事件,所以Header里的handleStorageChange函数根本不会执行。


解决方案

方案1:用自定义事件通知状态变更

在useLogout的回调里触发自定义事件,让Header组件同时监听storage和自定义事件来更新状态。

修改useLogout钩子:

export function useLogout() {
  return useMutation(() => AuthService.logout(), {
    onSuccess: () => {
      localStorage.removeItem("access");
      localStorage.removeItem("refresh");
      // 触发自定义事件通知状态变更
      window.dispatchEvent(new Event("localStorageUpdated"));
    },
    onError: (data) => console.log(data),
  });
}

修改Header组件的useEffect:

useEffect(() => {
  function handleStorageChange() {
    setIsLogin(!!localStorage.getItem("access"));
  }

  window.addEventListener("storage", handleStorageChange);
  // 添加自定义事件监听
  window.addEventListener("localStorageUpdated", handleStorageChange);

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

方案2:将登录状态提升到全局状态管理(推荐)

用React Context或Redux统一管理登录状态,登出时直接更新全局状态,Header组件直接消费状态,无需依赖localStorage监听。

示例(React Context实现):

  1. 创建AuthContext:
import { createContext, useContext, useState, useEffect, ReactNode } from "react";

interface AuthContextType {
  isLogin: boolean;
  logout: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [isLogin, setIsLogin] = useState<boolean>(!!localStorage.getItem("access"));

  // 监听其他页面的storage变更
  useEffect(() => {
    function handleStorageChange() {
      setIsLogin(!!localStorage.getItem("access"));
    }
    window.addEventListener("storage", handleStorageChange);
    return () => window.removeEventListener("storage", handleStorageChange);
  }, []);

  const logout = async () => {
    try {
      await AuthService.logout();
      localStorage.removeItem("access");
      localStorage.removeItem("refresh");
      setIsLogin(false); // 直接更新全局状态
    } catch (err) {
      console.log(err);
    }
  };

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

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
}
  1. 修改ProfileBar组件:
import { useAuth } from "src/contexts/AuthContext";

const ProfileBar = () => {
  const { logout } = useAuth();

  return (
    <div className='text-t-text-primary' onClick={() => logout()}>
      Logout
    </div>
  );
};

export default ProfileBar;
  1. 修改Header组件:
import SignInButton from "../UI/SignInButton/SignInButton";
import ProfileBar from "../UI/ProfileBar/ProfileBar";
import SignUpButton from "../UI/SignUpButton/SignUpButton";
import { useAuth } from "src/contexts/AuthContext";

const Header = () => {
  const { isLogin } = useAuth();

  return (
    <>
      <header className='sticky top-0 z-30'>
        <div
          className='
            flex justify-between px-12 max-xl:px-8 bg-t-header-bottom h-[70px] items-center max-md:px-4 border-b-2 border-b-t-header-bottom-border
          '
        >
          <div className='flex items-center max-lg:hidden'>
            {isLogin ? (
              <ProfileBar />
            ) : (
              <div className='flex gap-3'>
                <SignInButton />
                <SignUpButton />
              </div>
            )}
          </div>
        </div>
      </header>
    </>
  );
};

export default Header;

方案3:定时检查localStorage(不推荐)

设置定时器定期检查localStorage的变化,这种方式性能较差,仅作为临时备选:

useEffect(() => {
  const interval = setInterval(() => {
    setIsLogin(!!localStorage.getItem("access"));
  }, 1000); // 每秒检查一次

  return () => clearInterval(interval);
}, []);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:46:33