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

React Hook useState值在PayPal函数中被重置为初始值问题排查

问题原因及解决方案

核心问题:闭包陷阱

你遇到的问题是闭包导致的状态值过时。PayPal按钮的回调函数(createOrder、onApprove)以及handleUser函数在组件初始渲染时被创建,它们捕获了初始的quantity值(1)。即使后续你通过增减按钮更新了quantity的状态,这些回调函数仍然引用的是初始渲染时的旧值,而非当前最新的状态。


解决方案

方案1:使用Ref跟踪最新的quantity值

通过useRef创建一个引用,同步更新最新的quantity值,确保所有回调函数都能获取到当前的数量:

const PaypalCheckoutButton = (props) => {
  const { product } = props;
  const { user } = UserAuth();
  const [quantity, setQuantity] = useState(1);
  const quantityRef = useRef(quantity); // 创建ref
  const [paidFor, setPaidFor] = useState(false);
  const [error, setError] = useState(null);
  const navigate = useNavigate();

  // 同步ref与quantity状态
  useEffect(() => {
    quantityRef.current = quantity;
  }, [quantity]);

  // 原有增减函数不变
  const handleDecrement = () => {
    if (quantity > 1) {
      setQuantity((prevCount) => prevCount - 1);
    }
  };
  const handleIncrement = () => {
    if (quantity < 10) {
      setQuantity((prevCount) => prevCount + 1);
    }
  };

  useEffect(() => {
    console.log("Mounted");
  }, []);

  async function handleUser(orderID) {
    if (user) {
      const docRef = doc(db, "users", user.uid);
      console.log(quantityRef.current); // 使用ref.current
      const orderNumber = `order-${orderID}`;
      const docData = {
        [orderNumber]: {
          amount: quantityRef.current, // 这里也用ref.current
          paypalOrderID: orderID,
          destinations: product.name,
          price: product.price,
          Orderdate: new Date(),
        },
      };
      await setDoc(docRef, docData, { merge: true });
      console.log("User document was updated");
    } else {
      console.log("no user to update");
    }
  }

  const handleApprove = (orderID) => {
    setPaidFor(true);
    handleUser(orderID);
    navigate("/");
    if (paidFor) {
      alert("Thank you for your purchase !");
    }
  };

  if (error) {
    alert(error);
  }

  return (
    <PayPalScriptProvider>
      <StyledSection>
        <PayPalButtons
          style={{
            layout: "horizontal",
            height: 48,
            tagline: false,
            shape: "pill",
          }}
          onClick={(data, actions) => {
            const NoSeatsLeft = false;
            if (NoSeatsLeft) {
              setError("Unfortunately, there are no seats left for this specific trip.");
              return actions.reject();
            } else {
              return actions.resolve();
            }
          }}
          createOrder={(data, actions) => {
            return actions.order.create({
              purchase_units: [
                {
                  description: product.name,
                  amount: {
                    currency_code: "USD",
                    value: 250.0 * quantityRef.current, // 使用ref.current
                  },
                },
              ],
            });
          }}
          onApprove={async (data, actions) => {
            const order = await actions.order.capture();
            console.log("order", order);
            handleApprove(data.orderID);
          }}
          onCancel={() => {}}
          onError={(err) => {
            setError(err);
            console.log("PayPal error", err);
          }}
        />

        <div className="inputQuantity">
          <div className="buttons">
            <button type="button" onClick={handleDecrement} className="inputbutton">-</button>
            <div className="quantity">{quantity}</div>
            <button type="button" onClick={handleIncrement} className="inputbutton">+</button>
          </div>
          <span>Total: ${300 * quantity}</span>
        </div>
      </StyledSection>
    </PayPalScriptProvider>
  );
};

方案2:将quantity作为参数传递给handleUser

修改handleApprove和handleUser,直接将当前的quantity作为参数传入,避免依赖闭包:

const handleApprove = (orderID) => {
  setPaidFor(true);
  handleUser(orderID, quantity); // 传递当前quantity
  navigate("/");
  if (paidFor) {
    alert("Thank you for your purchase !");
  }
};

async function handleUser(orderID, currentQuantity) {
  if (user) {
    const docRef = doc(db, "users", user.uid);
    console.log(currentQuantity); // 使用传入的参数
    const orderNumber = `order-${orderID}`;
    const docData = {
      [orderNumber]: {
        amount: currentQuantity, // 使用传入的参数
        paypalOrderID: orderID,
        destinations: product.name,
        price: product.price,
        Orderdate: new Date(),
      },
    };
    await setDoc(docRef, docData, { merge: true });
    console.log("User document was updated");
  } else {
    console.log("no user to update");
  }
}

方案3:强制PayPal按钮重新渲染

给PayPalButtons组件添加一个包含quantity的key属性,确保每次数量变化时按钮重新渲染,从而更新所有回调函数:

<PayPalButtons
  key={`paypal-btn-${quantity}`} // 添加key
  // 原有属性不变
/>

额外说明

方案1是最稳妥的通用解决方案,能确保所有依赖quantity的地方都获取到最新值。方案2更简洁,适合只在handleUser中需要最新值的场景。方案3是快速修复,但频繁重新渲染按钮可能影响用户体验,建议仅在其他方案无效时使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 13:01:03