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
相关产品推荐
相关产品推荐

