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

Next.js中PayPal按钮无法获取用户输入金额的问题

问题原因

这是典型的闭包陷阱:createOrder 函数在组件初始渲染时被创建,捕获了当时的 state 值(0)。尽管后续用户输入会更新 state,但 PayPalButtons 组件未感知到 createOrder 函数的变化,依然使用初始创建的版本,导致始终读取初始值。

解决方案

方案1:使用 useRef 跟踪最新金额

通过 useRef 保存实时金额值,确保 createOrder 能访问到最新状态:

"use client";
import { useEffect, useState, useRef } from 'react';
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
import { TextField, Grid, Box } from '@mui/material';

const clientId = process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID as string;
if (!clientId) {
  throw new Error("NEXT_PUBLIC_PAYPAL_CLIENT_ID is not defined in the environment.");
}

const ivaString = process.env.NEXT_PUBLIC_IVA as string;
if (!ivaString) {
  throw new Error("NEXT_PUBLIC_IVA is not defined in the environment.");
}

const iva = parseFloat(ivaString);

const initialOptions = {
  clientId: clientId,
  currency: "EUR",
  intent: "capture",
};

export default function Paypal() {
  const [state, setState] = useState(0);
  const amountRef = useRef(state); // 用ref保存最新金额

  // 当state更新时同步ref的值
  useEffect(() => {
    amountRef.current = state;
  }, [state]);
  
  const handleAmountChange =  (event: React.ChangeEvent<HTMLInputElement>) => {
    setState(parseFloat(event.target.value));
  };


  const createOrder = async (data: any, actions: any) => {
    const currentAmount = amountRef.current; // 读取ref中的最新值
    const requestBody = {
      amount: currentAmount,
      tax: (iva * currentAmount) / 100,
    };

    const response = await fetch('/api/paypal/checkout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(requestBody)
    });

    if (!response.ok) {
      throw new Error('Failed to create order');
    }

    const responseData = await response.json();
    const id = responseData.id;
    return id; 
  };

  const onApprove = async (data: any, actions: any) => {
    console.log('onApprove', data);
    const captureResult = await actions.order.capture();
    console.log('Capture Result:', captureResult);
  };

  const onCancel = (data: any, actions: any) => {
    console.log('onCancel', data);
  };

  return (
    <div>
      <PayPalScriptProvider options={initialOptions}>
        <Grid container spacing={2}>
          <Grid item xs={12}>
            <TextField
              label="Amount"
              type="number"
              value={state}
              onChange={handleAmountChange}
              fullWidth
            />
          </Grid>
          <Grid item xs={12}>
            <PayPalButtons
              createOrder={createOrder}
              onApprove={onApprove}
              onCancel={onCancel}
            />
          </Grid>
        </Grid>
      </PayPalScriptProvider>
    </div>
  );
}

方案2:通过 key 强制重新渲染 PayPalButtons

当金额变化时,给 PayPalButtons 设置动态 key,让 React 重新挂载组件,从而使用最新的 createOrder 函数:

// ... 其他代码保持不变 ...

return (
  <div>
    <PayPalScriptProvider options={initialOptions}>
      <Grid container spacing={2}>
        <Grid item xs={12}>
          <TextField
            label="Amount"
            type="number"
            value={state}
            onChange={handleAmountChange}
            fullWidth
          />
        </Grid>
        <Grid item xs={12}>
          <PayPalButtons
            key={state} // 当state变化时,强制重新渲染组件
            createOrder={createOrder}
            onApprove={onApprove}
            onCancel={onCancel}
          />
        </Grid>
      </Grid>
    </PayPalScriptProvider>
  </div>
);
说明
  • 方案1更高效,避免了组件频繁重新挂载,适合大多数场景。
  • 方案2简单直接,但每次金额变化都会重新初始化PayPal按钮,可能带来轻微性能开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:24:49