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

React AWS认证重定向流程中useEffect执行两次的处理方案咨询

问题

我正在开发一个使用AWS Authentication(支持社交及非社交登录)的小型应用。在AWS的重定向登录流程中,我在useEffect内订阅登录事件,并通过POST请求向服务器创建用户。

但useEffect执行了两次(已通过日志确认),查Stack Overflow得知这是<React.StrictMode>导致的,且useEffect应保持纯函数特性。请问在此场景下,推荐的账户创建流程是什么?

现有代码

export default function LoginRedirect() {
    const _userService = new UserService();
    const navigator = useNavigate();

    useEffect(() => {
        console.log("activating useEffect in LoginRedirect");

        const getUserAndProcess = async (eventCancelToken: () => void) => {
            try {
                console.log("processing user...");
                const currentUser = await Auth.currentAuthenticatedUser() as CognitoUser
                if(currentUser != null) eventCancelToken() //stop listening to any more auth events
                else return;

                const userSession = currentUser.getSignInUserSession();
                const idToken = userSession?.getIdToken().getJwtToken();
                const accessToken = userSession?.getAccessToken().getJwtToken();
                const refreshToken = userSession?.getRefreshToken().getToken();

                if(idToken === undefined || accessToken === undefined || refreshToken === undefined) {
                    throw new Error("idToken, accessToken, or refreshToken is undefined");
                }

                const processUserResult = await _userService.processUser(
                    {
                        IdToken: idToken
                    } as ProcessUser
                );

                console.log(currentUser);
                console.log(processUserResult);

                navigator("/");

            } catch(error) {
                console.warn(error);
            }
        };

        const authListenerCancelToken = Hub.listen("auth", ({ payload: { event, data }}) => {
            switch (event) {
                case 'signIn':
                    console.log('Sign in happened!', data);
                    getUserAndProcess(authListenerCancelToken)
                        .catch(console.error);
                    break;
                case 'cognitoHostedUI':
                    console.log('Sign in happened via cognito hosted UI!', data);
                    break;
                case "signOut":
                    break;
                case 'signIn_failure':
                case 'cognitoHostedUI_failure':
                    console.log('Sign in failure', data);
                    break;
            }
        });

        getUserAndProcess(authListenerCancelToken).catch(console.error);
    });


  return (
      <div className="h-screen w-full grid place-items-center">
          <div className="grid place-items-center">
              <h1 className="text-lg my-3">Logging you in...</h1>
              <div className="loading loading-ring loading-lg justify-self-center"></div>
          </div>
      </div>
  );
}
推荐的账户创建流程及问题修复

1. 处理StrictMode导致的重复执行

StrictMode在开发环境下会刻意执行两次useEffect(含清理函数),用于检测潜在bug。要避免重复触发账户创建逻辑,需做以下调整:

  • 添加执行标记:用变量记录是否已处理过用户,防止重复调用processUser。
  • 完善清理逻辑:在useEffect的清理阶段调用Hub的取消监听函数,避免内存泄漏和重复监听。

修改后的useEffect示例:

useEffect(() => {
    let isUserProcessed = false; // 标记是否已完成用户处理
    console.log("activating useEffect in LoginRedirect");

    const getUserAndProcess = async (eventCancelToken: () => void) => {
        if (isUserProcessed) return; // 已处理过则直接返回
        try {
            console.log("processing user...");
            const currentUser = await Auth.currentAuthenticatedUser() as CognitoUser
            if(!currentUser) return;

            isUserProcessed = true; // 标记为已处理
            eventCancelToken(); // 停止监听后续auth事件

            const userSession = currentUser.getSignInUserSession();
            const idToken = userSession?.getIdToken().getJwtToken();
            const accessToken = userSession?.getAccessToken().getJwtToken();
            const refreshToken = userSession?.getRefreshToken().getToken();

            if(!idToken || !accessToken || !refreshToken) {
                throw new Error("idToken, accessToken, or refreshToken is undefined");
            }

            const processUserResult = await _userService.processUser(
                { IdToken: idToken } as ProcessUser
            );

            console.log(currentUser);
            console.log(processUserResult);

            navigator("/");

        } catch(error) {
            console.warn(error);
        }
    };

    const authListenerCancelToken = Hub.listen("auth", ({ payload: { event, data }}) => {
        switch (event) {
            case 'signIn':
            case 'cognitoHostedUI':
                console.log('Sign in happened!', data);
                getUserAndProcess(authListenerCancelToken).catch(console.error);
                break;
            case "signOut":
                break;
            case 'signIn_failure':
            case 'cognitoHostedUI_failure':
                console.log('Sign in failure', data);
                break;
        }
    });

    getUserAndProcess(authListenerCancelToken).catch(console.error);

    // 清理函数:取消auth事件监听
    return () => {
        authListenerCancelToken();
    };
}, []); // 空依赖数组确保useEffect仅在组件挂载时执行(开发环境下StrictMode会执行两次,但清理逻辑会同步触发)

2. 优化账户创建核心流程

  • 服务器端幂等性保障:即使前端因意外重复调用processUser,服务器需通过Cognito ID Token中的唯一标识(如sub字段)判断用户是否已存在,存在则返回现有信息,不存在再创建账户。
  • 分离逻辑职责:将用户处理逻辑抽离为独立工具函数或自定义Hook,保持组件简洁,同时便于复用和测试。
  • 全局状态管理:将用户信息存入Context或状态管理库(如Redux),避免在多个组件中重复获取用户数据。

3. 其他优化点

  • 缓存服务实例:避免在组件内部每次渲染都创建UserService实例,可将实例提升到组件外部,或用useMemo缓存:
    const _userService = useMemo(() => new UserService(), []);
    
  • 增强错误处理:针对不同类型的错误(如网络异常、Token无效)添加针对性提示,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:05:22