MrCodewide
MrCodewide

Reputation: 335

Paypal button cannot read new React state. How to work with dynamic values and paypal in React?

I'm currently working on the checkout page of an application where a user can purchase up to three items at one of three prices chosen by the user (this is mostly being done as an experiment). When the user chooses a price by clicking a button this triggers a setState and a new price is stored to the state. When doing console.log I see the new state has been set, but upon checkout it appears the state resets to its initial value. I can't tell why and have no idea where to begin on this one. I imagine on initial render paypal is keeping the initial state it was passed and needs to be rerendered when the new state is set, but not sure how to go about this or even if this is the problem. Any help or guidance is appreciated.

I'm using the @paypal/react-paypal-js library for this paypal implementation, but am welcome to alternative suggestions.

Here is the code I'm using but cut down relevant sections:

import React, {useState, useRef, useEffect} from 'react';
import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
import PriceButton from './PriceButton.jsx';
import NumberItemButton from './NumberItemButton';
import {priceOptions, amountItems} from './PriceOptions';



const PaymentPage = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [payAmount, setPayAmount] = useState('5.00');
  const [itemAmount, setItemAmount] = useState('1');

  const payPalOptions = { //Note: This is used in the higher level component PayPalScriptProvider
    "client-id": `${process.env.REACT_APP_PAYPAL_CLIENT_ID}`,
    currency: "USD",
    intent: "capture",
  };

  const createOrder = (data, actions) => { //This will show the initial state when triggered
    return actions.order.create({
      purchase_units : [
        {
          amount: {
            value: payAmount //This stays at the initial State of '5.00' despite the newState being set
          }
        }
      ]
    })
  };

  const onApprove = (data, actions) => {
    return actions.order.capture().then(function(orderData) {
          console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
          let transaction = orderData.purchase_units[0].payments.captures[0];
          alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
    }
  )};

  const onError = (error) => {
    console.log(error)
  }

  console.log(payAmount) //Note: This will show the new State
  return (
    <div>
        <h1>Purchase</h1>
        <label> Choose number of items
        <div>
          {amountItems.map((item, index) => {
            return <NumberItemButton key={index} setItemAmount={setItemAmount} amount={item.amount} />
          })}
        </div>
        </label>
        <label> Pick a price
          <div>
            {priceOptions.map((item, index) => {
              return <PriceButton key={index} itemAmount={itemAmount} setPayAmount={setPayAmount} price={item.price} />
            })}
          </div>
        </label>
        <PayPalButtons
          createOrder={(data, actions) => createOrder(data, actions)}
          onApprove={(data, actions) => onApprove(data, actions)}
          onError={onError}
        />  
    </div>
  );
}


export default PaymentPage;

I'll also add the price button component incase the issue is there

const PriceButton = ({itemAmount, setPayAmount, price}) => { //itemAmount is the amount customer buys, price is the value passed through on the mapping function
  const multPrice = (itemAmount * price).toFixed(2);
  const withTaxPrice = (parseInt(multPrice) + .5).toFixed(2).toString();

  return (
    <button onClick={() => setPayAmount(withTaxPrice)}>${multPrice}</button>
  )
}

export default PriceButton;

Appreciate any help!

Upvotes: 6

Views: 1882

Answers (3)

Kyung Lee
Kyung Lee

Reputation: 998

Use the forceReRender property.

Check out the official Storybook: https://paypal.github.io/react-paypal-js/?path=/docs/example-paypalbuttons--default

<PayPalButtons
    style={style}
    disabled={false}
    forceReRender={[amount, currency, style]} /* <= forceReRender */
    fundingSource={undefined}
    createOrder={(data, actions) => {
        return actions.order
            .create({
                purchase_units: [
                    {
                        amount: {
                            currency_code: currency,
                            value: amount,
                        },
                    },
                ],
            })
            .then((orderId) => {
                // Your code here after create the order
                return orderId;
            });
    }}
    onApprove={function (data, actions) {
        return actions.order.capture().then(function () {
            // Your code here after capture the order
        });
    }}
/>

Upvotes: 2

Balthazar Robin
Balthazar Robin

Reputation: 41

I found a better solution. Just use useRef and access the ref.current value!

Upvotes: 4

MrCodewide
MrCodewide

Reputation: 335

I came back to this with a fresh pair of eyes and found the solution (though I'm not sure if it's the best one).

The issue is when the Paypal button renders it pulls in the initial state that is passed through, but it needs to be rerendered when a new state is passed.

My solution to this was to pass a forceReRender={[payAmount]} within the PaypalButtons component. This rerenders the Paypal button upon update to the price state and allows me to pass an updated value.

Hope this helps others!

Upvotes: 14

Related Questions