react-stripe-js: [BUG]: Elements component can create multiple instances of stripeJs.StripeElements

What happened?

Recent changes to this library have changed when and how often the useEffect which creates the StripeJs.StripeElements instance runs.

Because of the addition of options to the dependency list, it reruns on every render for those who have configured the Elements component with a non-memoized set of options. This a a reasonable assumption since:

  1. The examples in the docs do it this way
  2. The docs state “Once the stripe prop has been set, these options can’t be changed.” implying they are basically read once.

If we pass a promise to the Elements stripe attribute, and there are multiple renders prior to that promise resolving, the effect will run multiple times and attached multiple resolvers to the promise and therefore create multiple elements instances.

In the typical flow, the first resolution will create any child PaymentElements attached to the first elements instance. Subsequent useElements in child instances will get the newer elements instances after that.

When trying to checkout (confirmPayment with useElements), the checkout fails and we then get the error “Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element.”

For others facing this issue, one of many ways to work around it is to wrap the options argument in a useMemo, or to pass a Stripe instance to Elements component rather than a Stripe promise.

Environment

No response

Reproduction

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 8
  • Comments: 23 (6 by maintainers)

Most upvoted comments

We had a same issue as @martin3walker.

Code snippet which was breaking elements component:

if (isLoading) {          <---------------- removing this if statement solves the problem
 return <Spinner />
 }
 
 return (
 ...
 <PaymentElement />
 ...
 )

Hello,

I’m running into this same error: IntegrationError: Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element or Pay Button Element. I’m using version 1.16.0 of @stripe/react-stripe-js.

Following the workaround advice above, I’m passing in a resolved instance of the stripe.js SDK into the stripe prop of the Elements provider. I’m also memoizing the options object where I pass in the client secret.

const PaymentDetails = ({
  formState,
  setIsLoading,
  setCurrentFormStep,
  stripe,
}: FormStepProps) => {
  const options = useMemo(() => {
    const { clientSecret } = formState;
    return {
      clientSecret,
    };
  }, [formState]);

  return (
    <Elements stripe={stripe} options={options}>
        <StripePaymentForm />
    </Elements>
 )

In the <StripePaymentForm /> component I’m calling the following click handler to submit the data from a <PaymentElement /> :

const clickHandler = async (event: any) => {
    event.preventDefault();
    setIsLoading(true);

    if (!stripe || !elements) {
      return;
    }

    try {
      await stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: window.location.href,
        },
        redirect: 'if_required',
      });

    } catch (error) {
      console.log(error);
    }
  };

The error is being thrown at stripe.confirmPayment. The <PaymentElement /> is rendered, so I’m confused as to why it says it isn’t mounted.

Do we think this is the same bug? Perhaps it is a mistake on my end as I’m relatively new to Stripe 🤷

@mario-garcia:

And I don’t really understand the “get rid of the loader/spinner” suggestion

The PaymentElement has to be mounted and on the page when calling stripe.confirmPayment. The folks above were replacing the PaymentElement with a load spinner in their pay/click handler, which was causing the IntegrationError: Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element or Pay Button Element. error.

Let me know if that clarification helps.

My problem ended up being unrelated to this. I set a loading state in the click handler that was replacing the PaymentElement with a loading component. This was messing with Stripe’s flow. Just FYI in case anyone else runs into a similar issue.

FWIW I hit this a couple days ago too. Details here: https://stackoverflow.com/q/72334261/65387

I had the same issue.

Turns out I had accidentally used <Elements></Elements> twice while adapting the official example to my needs.

Did not have to useMemo or the like.

Dropping back to “@stripe/react-stripe-js”: “^1.7.1” until this is fixed.

aaaaahhh. thanks @bmathews-stripe. that clarifies it and indeed was my issue. thanks again.

@randypuro and @mnpenner: We’ve released a (likely) fix in v1.8.1. Thanks!

React 18.1, not using StrictMode.