简体   繁体   中英

PayPal Smart Payment Buttons using old State

So the scenario is I have a React + Redux ECommerce store and there is a Checkout Page where all the items in the cart are listed. This works fine, the buttons get re-rendered every time I add a product, however, there is the option to delete items from the cart on the checkout page, and although the actual state changes, the Paypal buttons use the old state (I suppose because the buttons don't get re-rendered). I have wrapped the buttons in a useEffect function that should run every time there is a state change with the items in the cart, however, this is not the case and I just can't figure out why.

Here is the code:

import {useSelector, useDispatch} from 'react-redux'
import {removeProduct} from '../redux/cart/cart.actions'
import './Cart.scss'
import CartItem from '../components/cartComponent/cartItem'


const mapState = ({ cart }) => ({
    itemsInCart: cart.itemsInCart,
    total: cart.total

})

const Cart = props => {

    const [paidFor, setPaidFor] = useState(false);
    const dispatch = useDispatch();
    const {itemsInCart} = useSelector(mapState);
    const {total} = useSelector(mapState);
    let paypalRef = useRef();

    const clickHandler = (e) => {
        e.preventDefault();
        const removeIndex = itemsInCart.findIndex(item => {
           return item.id === e.target.id
    })
        dispatch(removeProduct(removeIndex,itemsInCart[removeIndex]))
    }

        useEffect(() => {
            window.paypal
              .Buttons({
                createOrder: (data, actions) => {
                  return actions.order.create({
                    purchase_units: [
                      {
                        description: "product.description",
                        amount: {
                          currency_code: 'USD',
                          value: total,
                        },
                      },
                    ],
                  });
                },
                onApprove: async (data, actions) => {
                  const order = await actions.order.capture();
                  setPaidFor(true);
                  console.log(order);
                },
                onError: err => {
                  setError(err);
                  console.error(err);
                },
              })
              .render(paypalRef.current);
          },[itemsInCart]);
                
    return (
        <>
        {paidFor ? (
            <div><h1>Congrats you just bought an Article</h1></div>
        ):
        <>
        <h1>Your Shopping Cart</h1>
        <div className = "main">
            <form className="cart-area">
                <ul className="cart-ui">
                     {itemsInCart.map((item, index)=> {
                        return <CartItem
                        clicked={clickHandler}
                        name={item.name}
                        price={item.price}
                        id={item.id}
                        key={index}
                        />
                     })}
                </ul>
 
                <div className="cart-summary">
                    <div className ="cart-wrapper">
                     <div className="name-price-wrapper">
                     <p className="name">Items</p>
                     <p className="price">{total}€</p>
                     </div>
                     <div ref={paypalRef}></div>
                     </div>
                </div>
            </form>
        </div>
        </>
        } 
     </>
    )
}
export default Cart;

When I change the useEffect hook to eg always rerender, I get the buttons multiple time (depending on how many items I delete) and when clicking the button on the bottom it actually works with the right amount. So I have to close the previous buttons because they get rendered.

When following the referenced post from @Preston PHX my code looks like:

useEffect(() => {
          if(window.myButton) window.paypal.close();
          window.myButton = window.paypal
              .Buttons({
                createOrder: (data, actions) => {
                    console.log(total)
                  return actions.order.create({
                    purchase_units: [
                      {
                        description: "product.description",
                        amount: {
                          currency_code: 'USD',
                          value: total,
                        },
                      },
                    ],
                  });
                },
                onApprove: async (data, actions) => {
                  const order = await actions.order.capture();
                  setPaidFor(true);
                  console.log(order);
                },
                onError: err => {
                  setError(err);
                  console.error(err);
                },
              })
              .render(paypalRef.current)

          });

Here I get the error window.myButton.close() is not a function ...?

You are misunderstanding the parameter to useEffect: https://reactjs.org/docs/hooks-effect.html

It will cause the effect to only run if that parameter changed

It won't cause the effect to run whenever that parameter changes


Another possible issue is closing the existing buttons, if you really are re-rendering them. Re-rendering them shouldn't be necessary if your purchase_units object references dynamic variables/functions that are evaluated when the buttons are clicked. However, if you are re-rendering the buttons, you may need to close the existing buttons. You can do this by storing a reference to window.PayPal.Buttons(...) before calling.render on it, so that you can then.close() if defined, before calling.render the second time around.

An answer on how to do that: https://stackoverflow.com/a/62349799/2069605

import React from "react";
import ReactDOM from "react-dom"

const PayPalButton = paypal.Buttons.driver("react", { React, ReactDOM });

function YourComponent({price}) {
  const createOrder = (data, actions) =>{
    return actions.order.create({
      purchase_units: [
        {
          amount: {
            value: `${price}`,
          },
        },
      ],
    });
  };

  const onApprove = (data, actions) => {
    return actions.order.capture();
  };

  return (
    <PayPalButton
      createOrder={(data, actions) => createOrder(data, actions)}
      onApprove={(data, actions) => onApprove(data, actions)}
    />
  );
}

Click here for the Paypal documentation

OR try using useRef hook

import React, { useRef, useEffect } from 'react';

const Paypal = ({ amount }) => {
  const paypal = useRef();
  useEffect(() => {
    if (window.paypalBtn) window.paypalBtn.close();
    window.paypalBtn = window.paypal.Buttons({
      createOrder: function (data, actions) {
        // This function sets up the details of the transaction, including the amount and line item details.
        return actions.order.create({
          purchase_units: [
            {
              amount: {
                value: `${amount}`,
              },
            },
          ],
        });
      },
      onApprove: function (data, actions) {
        // This function captures the funds from the transaction.
        return actions.order.capture().then(function (details) {
          // This function shows a transaction success message to your buyer.
        console.log('details', details);
        alert('Transaction completed by ' + details.payer.name.given_name);
        });
      },
      onError: function (err) {
        console.error(err);
      },
    });
    window.paypalBtn.render(paypal.current);
  }, [amount]);

  return (
    <div>
      <div ref={paypal}></div>
    </div>
  );
};

export default Paypal;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM