如何在不重新渲染的情况下将动态金额传递给 Paypal React SDK Button

How to pass dynamic amount to Paypal React SDK Button without re-render

我正在使用 react-paypal-js,需要在点击 PayPal 按钮时设置一个动态金额。

问题:createOrder 函数无法从我的状态中获取数据(可能是因为它在 PayPal 脚本上下文中)。

无效示例:

    const [amount, setAmount] = useState('');

    const handleCreatePaypal = (data, actions) => {
        const orderAmount = parseFloat(amount).toFixed(2) || '1.00'; //amount is the state
        console.log(amount, orderAmount)
        return actions.order.create({
            purchase_units: [
                { amount: { value: orderAmount } }
            ]
        })
    }

    return (
        <PayPalScriptProvider options={paypalOptions}>
            <PayPalButtons
                createOrder={(data, actions) => handleCreatePaypal(data, actions)}
                onApprove={(data, actions) => handlePaypalApprove(data, actions)}
                onError={handlePaymentError}
            />
        </PayPalScriptProvider>
    )

不完美的解决方案:

  1. HTML获取金额,但我真的不想那样做。
    const amountRef = useRef(null);  //ref on input not showing here
    ...
    const orderAmount = parseFloat(amountRef.current.value).toFixed(2) || '1.00'; //get value from html
    ...

  1. 当数量改变时强制重新渲染。但这将导致按钮消失并在金额更改时再次显示。
        <PayPalScriptProvider options={paypalOptions}>
            <PayPalButtons
                createOrder={(data, actions) => handleCreatePaypal(data, actions)}
                forceReRender={[amount]}  //force re-render when amount changes
            />
        </PayPalScriptProvider>

有解决这个问题的方法吗?提前致谢!

如果没有解决方案,我可能需要从后端集成:(

我不想让它太复杂,因为它只是一个简单的捐赠按钮。

一般来说,从后端集成是最强大的解决方案,尤其是对于电子商务而言——但由于这是一个简单的捐赠用例,我知道您可能希望如何避免这种情况。

有多种可能的解决方案,包括使用额外的状态框架,例如 Redux,但您似乎想要做的是在按钮上方的父组件级别管理状态,并且 bind 它的 createOrder (“handleCreatePayPal”)方法等,以便 this 在上下文中并且它们可以访问 its 状态。

Here is a runnable codesandbox example (based on the documented example that uses a re-render,不过none这里就完成了)

import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
import * as React from "react";

const debug = true;

const initialState = {
  amount: "2.00",
  orderID: "",
  onApproveMessage: "",
  onErrorMessage: ""
};

export default class App extends React.Component<{}, typeof initialState> {
  constructor(props: any) {
    super(props);
    this.state = initialState;

    this.onChange = this.onChange.bind(this);
    this.createOrder = this.createOrder.bind(this);
    this.onApprove = this.onApprove.bind(this);
    this.onError = this.onError.bind(this);
    this.onClick = this.onClick.bind(this);
  }

  onChange(event: React.ChangeEvent<HTMLSelectElement>) {
    this.setState({
      amount: event.target.value,
      orderID: "",
      onApproveMessage: "",
      onErrorMessage: ""
    });
  }

  createOrder(data: Record<string, unknown>, actions: any) {
    if (debug) console.log("Creating order for amount", this.state.amount);
    return actions.order
      .create({
        purchase_units: [
          {
            amount: {
              value: this.state.amount
            }
          }
        ]
      })
      .then((orderID: any) => {
        this.setState({ orderID: orderID });
        return orderID;
      });
  }

  onApprove(data: any, actions: any) {
    let app = this;
    return actions.order.capture().then(function (details: any) {
      app.setState({
        onApproveMessage: `Transaction completed by ${details.payer.name.given_name}!`
      });
    });
  }

  onError(err: Record<string, unknown>) {
    this.setState({
      onErrorMessage: err.toString()
    });
  }

  onClick() {
    if (debug) console.log("When clicked, amount was", this.state.amount);
  }

  render() {
    return (
      <div style={{ minHeight: "300px" }}>
        <table className="table" style={{ maxWidth: "400px" }}>
          <tbody>
            <tr>
              <th>
                <label htmlFor="amount">Order Amount: </label>
              </th>
              <td>
                <select onChange={this.onChange} name="amount" id="amount">
                  <option value="2.00">.00</option>
                  <option value="4.00">.00</option>
                  <option value="6.00">.00</option>
                </select>
              </td>
            </tr>
            <tr>
              <th>Order ID:</th>
              <td>{this.state.orderID ? this.state.orderID : "unknown"}</td>
            </tr>
            <tr>
              <th>On Approve Message: </th>
              <td data-testid="message">{this.state.onApproveMessage}</td>
            </tr>
            <tr>
              <th>On Error Message: </th>
              <td data-testid="error">{this.state.onErrorMessage}</td>
            </tr>
          </tbody>
        </table>
        <PayPalScriptProvider options={{ "client-id": "test" }}>
          <PayPalButtons
            createOrder={this.createOrder}
            onApprove={this.onApprove}
            onError={this.onError}
            onClick={this.onClick}
          />
        </PayPalScriptProvider>
      </div>
    );
  }
}