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

React项目注册新用户时重复生成Braintree客户ID问题

注册用户时重复生成Braintree客户ID问题排查

enter image description here

我正在开发一个React项目,每次注册新用户时都会生成两个Braintree客户ID,而预期应为每个用户仅生成一个。

以下是我的代码:

const ConfirmEmail = ({ handleItems, onSubmitUser }) => {
  let params = useParams();
  
  let isMounted = true;
  const [loading, setLoading] = useState(true);
  
  useCallback(() => {
    fetchData();
  }, [isMounted]);
  
  const fetchData = async () => {
    try {
      if (JSON.parse(localStorage.getItem("user"))) return;
      const response = await UserService.confirmEmail(params.tokenEmail);
      if (response && response.data && !response.err) {
        const { user, accessToken } = response.data;
        localStorage.setItem("accessToken", accessToken);
        if (isMounted) {
          handleItems();
          onSubmitUser(user);
        }
      }
      if (isMounted) setLoading(false);
    } catch (err) {
      const { response } = err;
      if (response?.data?.data) toast.error(response.data.data);
    }

    // Unmounted

    return (isMounted = false);
  };

问题根源分析

  1. 错误使用useCallback触发请求:useCallback用于缓存函数,不是处理组件初始化副作用的钩子,用它触发fetchData会导致函数重复执行。尤其isMounted是普通变量,每次组件重渲染都会重置,可能触发多次请求。
  2. isMounted变量管理不规范:普通变量isMounted在组件重渲染时会被重新赋值为true,无法准确跟踪组件挂载状态,可能导致异步请求完成后重复执行onSubmitUser(该函数大概率包含生成Braintree客户ID的逻辑)。
  3. 缺少请求去重逻辑:没有确保fetchData只执行一次,组件意外重渲染时会再次发起请求,进而重复生成客户ID。

修复方案

改用useEffect处理初始化请求,配合useRef管理挂载状态,确保请求仅执行一次:

const ConfirmEmail = ({ handleItems, onSubmitUser }) => {
  const params = useParams();
  const [loading, setLoading] = useState(true);
  // 用Ref跟踪组件挂载状态,避免重渲染重置
  const isMountedRef = useRef(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        // 本地已有用户数据时直接终止,避免重复请求
        const storedUser = localStorage.getItem("user");
        if (storedUser && JSON.parse(storedUser)) {
          setLoading(false);
          return;
        }

        const response = await UserService.confirmEmail(params.tokenEmail);
        if (response && response.data && !response.err) {
          const { user, accessToken } = response.data;
          localStorage.setItem("accessToken", accessToken);
          // 确保组件仍挂载时才执行后续操作
          if (isMountedRef.current) {
            handleItems();
            onSubmitUser(user);
          }
        }
      } catch (err) {
        const { response } = err;
        if (response?.data?.data) toast.error(response.data.data);
      } finally {
        // 无论请求成功失败,都更新loading状态(仅组件挂载时)
        if (isMountedRef.current) setLoading(false);
      }
    };

    fetchData();

    // 组件卸载时更新挂载状态
    return () => {
      isMountedRef.current = false;
    };
  }, [params.tokenEmail, handleItems, onSubmitUser]); // 依赖数组确保只有必要时重新执行

  // 组件渲染逻辑...
};

关键修改点说明

  • 用useEffect替代useCallback:useEffect专门处理组件挂载/更新时的副作用,确保初始化请求只执行一次(除非依赖变化)。
  • useRef管理挂载状态:useRef的current属性在组件生命周期内保持稳定,不会因重渲染重置,能准确判断组件是否还在挂载中。
  • 增加请求终止逻辑:本地已有用户数据时直接终止请求,避免重复执行后续流程。
  • finally块统一处理loading状态:确保无论请求成功或失败,都能正确更新loading状态,同时避免在组件卸载后更新状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 17:23:38