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

自定义useAuth Hook登录后组件中user状态未更新求助

问题描述

实现了一个用于客户端登录的useAuth Hook,通过localStorage存储用户信息。调用login方法后,localStorage中已存入用户信息,Hook内部的user状态也已更新,但在使用该Hook的其他组件中,获取到的user并未同步更新。

相关代码

useAuth Hook代码

const useAuth = () => {

    const localStorageUserKey = "@mysite:user"

    const [user, setUser] = useState<IUser | null>(null);
    const navigate = useNavigate();

    useEffect(() => {
        const userJson = window.localStorage.getItem(localStorageUserKey);

        if (userJson) {
            setUser(JSON.parse(userJson));
        }
    }, []);

    const login = (newUser: IUser) => {
        window.localStorage.setItem(localStorageUserKey, JSON.stringify(newUser));
        setUser(newUser);
    }

    const logout = () => {
        window.localStorage.removeItem(localStorageUserKey);
        setUser(null);
        navigate("/");
    }

    return { login, logout, user }
}

export default useAuth;

LoginPage组件使用代码

export default function LoginPage() {
    const { post } = useApi();
    const { login } = useAuth();
    const [error, setError] = useState<string>("");
    const navigate = useNavigate()

    const onSubmit = (email: string, password: string) => {

        setError("");

        post<ILoginApiResponse>("/login", { email, password }).then((r) => {
            if (r.data.user) {
                login(r.data.user);
            }
        }).catch((r) => {
            if (r.response.status === 400) {
                setError("Identifiants invalides");
            } else {
                setError("Une erreur est survenue");
            }
        });
    }

    return (
        <div>
            <h2>Connexion</h2>
            {error ? <ErrorMessage message={error} /> : null}
            <LoginForm onSubmit={onSubmit} />
        </div>
    );
}

Layout组件使用user的示例代码

export default function Layout(props: ILayoutProps) {

    const { user, logout } = useAuth();

    return <div className="main-container">
        <header className="header-container">
            <nav>
                <ul className="header-nav">
                    /* ... */
                    {!user ? 
                        <li className="item">
                          <a className="header-nav-link" href="/login">Connexion</a>
                        </li> : 
                        <li>
                        <button className="header-nav-link" onClick={logout}>Se déconnecter</button>
                        </li>
                    }
                </ul>
            </nav>
        </header>
        <Outlet />
    </div>;

}
成因分析
  • 当前的useAuth Hook是无状态共享的独立实例:每次组件调用useAuth()时,都会创建全新的useState状态和副作用逻辑。
  • 当LoginPage调用login方法时,更新的只是LoginPage内部useAuth实例的user状态;而Layout组件里的useAuth是另一个独立实例,它的user状态只会在组件挂载时通过useEffect读取一次localStorage,后续不会自动感知其他实例对localStorage的修改。
解决方案

要实现跨组件的状态同步,需要把用户认证状态提升为全局共享状态,以下是两种常用方案:

方案1:使用React Context封装认证状态

把useAuth的逻辑封装到Context中,让所有组件共享同一个状态实例:

  1. 创建带Context的useAuth:
import { createContext, useContext, useState, useEffect, useNavigate } from 'react';
import type { IUser } from './types';

const AuthContext = createContext<{
  user: IUser | null;
  login: (user: IUser) => void;
  logout: () => void;
} | null>(null);

export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
  const localStorageUserKey = "@mysite:user";
  const [user, setUser] = useState<IUser | null>(null);
  const navigate = useNavigate();

  useEffect(() => {
    const userJson = window.localStorage.getItem(localStorageUserKey);
    if (userJson) {
      setUser(JSON.parse(userJson));
    }
  }, []);

  const login = (newUser: IUser) => {
    window.localStorage.setItem(localStorageUserKey, JSON.stringify(newUser));
    setUser(newUser);
  };

  const logout = () => {
    window.localStorage.removeItem(localStorageUserKey);
    setUser(null);
    navigate("/");
  };

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

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};
  1. 在应用根组件中包裹AuthProvider:
import { AuthProvider } from './useAuth';
import { BrowserRouter } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <AuthProvider>
        {/* 你的路由和组件 */}
      </AuthProvider>
    </BrowserRouter>
  );
}
  1. 组件中使用方式不变,依然调用useAuth(),此时所有组件共享同一个状态实例,状态更新会同步到所有使用该Hook的组件。

方案2:监听localStorage变化(适合简单场景)

如果不想用Context,可以在useAuth中添加localStorage的监听,当其他地方修改localStorage时自动更新状态:

const useAuth = () => {
  const localStorageUserKey = "@mysite:user";
  const [user, setUser] = useState<IUser | null>(null);
  const navigate = useNavigate();

  const updateUserFromLocalStorage = () => {
    const userJson = window.localStorage.getItem(localStorageUserKey);
    if (userJson) {
      setUser(JSON.parse(userJson));
    } else {
      setUser(null);
    }
  };

  useEffect(() => {
    updateUserFromLocalStorage();
    // 监听localStorage变化
    window.addEventListener('storage', updateUserFromLocalStorage);
    return () => {
      window.removeEventListener('storage', updateUserFromLocalStorage);
    };
  }, []);

  const login = (newUser: IUser) => {
    window.localStorage.setItem(localStorageUserKey, JSON.stringify(newUser));
    setUser(newUser);
  };

  const logout = () => {
    window.localStorage.removeItem(localStorageUserKey);
    setUser(null);
    navigate("/");
  };

  return { login, logout, user };
};

注意:storage事件只有在不同标签页/窗口修改localStorage时才会触发,同一页面内的修改不会触发,所以需要在login和logout中手动调用setUser,同时保留监听处理跨标签页的同步。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 01:20:39