stripe/react-stripe-js

[BUG]: IntegrationError: Invalid value for stripe.confirmSetup(): elements should have a mounted Payment Element or Express Checkout Element.

Closed this issue · 2 comments

What happened?

I have developed a form to save payment details for the user.

I'm rendering this form inside a Mui Popup component on my page.

When I try to fill details submit for the first time I get this error: "An unhandled error was caught from submitForm() IntegrationError: Invalid value for stripe.confirmSetup(): elements should have a mounted Payment Element or Express Checkout Element."
When I try for the second time it works. Can you help me with this problem.

I'm pasting the working code below

Note: I observed that if comment setIsSubmitting setter functions, it works fine. But when I set state its having this issue.

import { Formik, useFormikContext } from 'formik'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useAddressOptions } from '../../../hooks'
import useCreateNewAddress from '../../../hooks/useCreateNewAddress'
import { PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'

import {
  AddressForm,
  Button,
  Error,
  FormGroup,
  MenuDropdown,
} from '../../../components'
import { Plus } from '../../../icons'

import { CLR_PRIMARY } from '../../../constants/colors'

const DEFAULT_RETURN_URL = '/my-profile/payment-details'

function PaymentForm({ onClose, returnTo, zIndex }) {
  const stripe = useStripe()
  const elements = useElements()
  const { t } = useTranslation(['myProfile'])

  const { defaultBillingAddress } = useAddressOptions()
  const [showAddressForm, setShowAddressForm] = useState(false)
  const [errorMessage, setErrorMessage] = useState(null)
  const [isSubmitting, setIsSubmitting] = useState(false)

  const {
    mutate: createNewAddress,
    isLoading,
    isError,
  } = useCreateNewAddress(
    // onSuccess
    () => {
      closeAddressForm()
    }
  )

  const openAddressForm = () => {
    setShowAddressForm(true)
  }

  const closeAddressForm = () => {
    setShowAddressForm(false)
  }

  const validateForm = (values) => {
    return validate(values, t)
  }

  const handleFormSubmit = async (values) => {
    const { name, selectedAddress, isDefaultPaymentMethod } = values

    setErrorMessage(null)
    setIsSubmitting(false)

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

    setIsSubmitting(true)

    const urlPath = returnTo ? returnTo : DEFAULT_RETURN_URL
    let returnURL = `${window.location.origin}${urlPath}`
    if (isDefaultPaymentMethod) {
      returnURL += '?default_payment_method=true'
    }

    const { error } = await stripe.confirmSetup({
      elements,
      confirmParams: {
        return_url: returnURL,
        payment_method_data: {
          billing_details: {
            name,
            address: {
              city: selectedAddress?.value?.city,
              country: selectedAddress?.value?.countryCode,
              line1: selectedAddress?.value?.address,
              line2: selectedAddress?.value?.flatSuite,
              postal_code: selectedAddress?.value?.zipCode,
              state: selectedAddress?.value?.stateCode,
            },
          },
        },
      },
    })

    setIsSubmitting(false)
    if (error) {
      setErrorMessage(error.message)
    }
  }

  const isSaveBtnDisabled = !stripe

  return (
    <Formik
      initialValues={{
        name: '',
        selectedAddress: defaultBillingAddress,
        isDefaultPaymentMethod: false,
      }}
      onSubmit={handleFormSubmit}
      validate={validateForm}
      validateOnChange={true}
    >
      {({
        values,
        errors,
        touched,
        handleChange,
        handleSubmit,
        handleBlur,
      }) => (
        <form className="payment-details__form" onSubmit={handleSubmit}>
          <div className="payment-details__method-info">
            <div className="payment-details__left-col">
              <FormGroup>
                <FormGroup.Label htmlFor="name">
                  {t('fullName')}
                </FormGroup.Label>

                <FormGroup.Input
                  id="name"
                  name="name"
                  value={values?.name}
                  onChange={handleChange}
                  onBlur={handleBlur}
                />

                {touched.name && errors?.name ? (
                  <Error mt="0.4rem">{errors?.name}</Error>
                ) : null}
              </FormGroup>

              <PaymentElement
                options={{
                  fields: {
                    billingDetails: {
                      address: {
                        country: 'never',
                        postalCode: 'never',
                      },
                    },
                  },
                }}
              />

              <FormGroup className="payment-details__default-payment">
                <FormGroup.Input
                  type="checkbox"
                  id="default-payment-method"
                  name="isDefaultPaymentMethod"
                  checked={values?.isDefaultPaymentMethod}
                  onChange={handleChange}
                />
                <FormGroup.Label htmlFor="default-payment-method">
                  {t('setAsDefaultPaymentMethod')}
                </FormGroup.Label>
              </FormGroup>
            </div>

            <FormGroup className="payment-details__billing-details">
              <FormGroup.Label>{t('billingAddress')}</FormGroup.Label>
              <AddressField />
              {touched.selectedAddress && errors?.selectedAddress ? (
                <Error mt="0.4rem">{errors?.selectedAddress}</Error>
              ) : null}

              <AddressForm
                isOpen={showAddressForm}
                handleClose={closeAddressForm}
                title={t('addBillingAddress')}
                error={t('genericErrMsg')}
                isError={isError}
                isLoading={isLoading}
                action={createNewAddress}
                zIndex={zIndex}
                isBillingAddress
              />

              <Button primaryBordered onClick={openAddressForm} type="button">
                {t('addNewAddress')} <Plus color={CLR_PRIMARY} />
              </Button>
            </FormGroup>
          </div>

          <ErrorMessage errorMessage={errorMessage} />
          <Footer
            onClose={onClose}
            isSaveBtnDisabled={isSaveBtnDisabled}
            isSubmitting={isSubmitting}
          />
        </form>
      )}
    </Formik>
  )
}

function AddressField() {
  const { addresses, defaultBillingAddress } = useAddressOptions()
  const { values, setFieldValue, touched, setTouched } = useFormikContext()

  useEffect(() => {
    setFieldValue('selectedAddress', defaultBillingAddress)
  }, [defaultBillingAddress, setFieldValue])

  return (
    <MenuDropdown
      options={addresses}
      value={values?.selectedAddress}
      onChange={(newAddress) => {
        setFieldValue('selectedAddress', newAddress)
      }}
      onBlur={() => {
        if (!touched?.selectedAddress) {
          setTouched({ selectedAddress: true })
        }
      }}
    />
  )
}

function ErrorMessage({ errorMessage }) {
  if (!errorMessage) return null

  return (
    <Error centered mt="1.5rem" mb="-1rem">
      {errorMessage}
    </Error>
  )
}

function Footer({ onClose, isSaveBtnDisabled, isSubmitting }) {
  const { t } = useTranslation(['myProfile'])

  return (
    <footer className="payment-details__footer">
      <div>
        <Button primaryBordered onClick={onClose}>
          {t('cancel')}
        </Button>
        <Button
          secondary
          type="submit"
          disabled={isSaveBtnDisabled}
          loading={isSubmitting}
        >
          {t('save')}
        </Button>
      </div>
    </footer>
  )
}

function validate(values, t) {
  const { name, selectedAddress } = values
  const errors = {}

  if (!name || name?.trim() === '') {
    errors.name = t('nameIsIncomplete')
  }

  if (
    !selectedAddress ||
    !selectedAddress?.value ||
    Object.keys(selectedAddress?.value).length === 0
  ) {
    errors.selectedAddress = t('billingAddressIsIncomplete')
  }

  return errors
}

export default PaymentForm

Environment

Chrome on Windows 10

Reproduction

No response

Hi, this looks like an integration question, and we aren’t able to answer those here on GitHub. Please reach out to Stripe Support for help.

Hi @fruchtose-stripe It seems like its an issue from stripe. I saw similar issue reported in past hence raised the issue.
Can you please check it once?