Issue with Nextjs and Stripe
Answered
Berger Picard posted this in #help-forum
Berger PicardOP
Sorry for asking for this in here but Stripe doesn't offer any community and I think the bug is somehow related to Nextjs and maybe Shadcn. Please bear with me.
I'm trying to create a add payment method form by using Stripe elements and Shadcn's dialog but for unknown reasons the following hooks:
- useElements
- useStripe
returns
Here's what I got so far by debugging:
- loadStripe resolves correctly (I did a console log by checking it)
- I tried to upgrade to Nextjs 15 and still the issue persists (nextjs 15 doesnt offer any cache by default I guess). I'm on Nextjs 14.2.13
- dynamic((), { ssr: false }) doesn't solve the issue
- I tried adding a useEffect to call the loadStripe and set it in a useState but also doesn't solve the issue
- I tried calling the
- I've also seen this StackOverflow [thread](https://stackoverflow.com/questions/77615172/stripe-usestripe-and-useelements-both-are-null-and-elements-do-not-render) but the answer is too vague
+
I'm trying to create a add payment method form by using Stripe elements and Shadcn's dialog but for unknown reasons the following hooks:
- useElements
- useStripe
returns
null
and simply doesn't work when trying to submit the form. And also, when I modify any part of the code that forces a hot reload it magically starts working again.Here's what I got so far by debugging:
- loadStripe resolves correctly (I did a console log by checking it)
- I tried to upgrade to Nextjs 15 and still the issue persists (nextjs 15 doesnt offer any cache by default I guess). I'm on Nextjs 14.2.13
- dynamic((), { ssr: false }) doesn't solve the issue
- I tried adding a useEffect to call the loadStripe and set it in a useState but also doesn't solve the issue
- I tried calling the
loadStripe
at the main component and passing it by props to the <Elements> but still doesn't solve the issue- I've also seen this StackOverflow [thread](https://stackoverflow.com/questions/77615172/stripe-usestripe-and-useelements-both-are-null-and-elements-do-not-render) but the answer is too vague
+
Answered by Berger Picard
found it, the useCallback didnt contain the stripe and elements as a dependency
6 Replies
Berger PicardOP
shadcn's dialog:
'use client'
// redacted imports to improve clarity
export type AddPaymentMethodProps = {
open: boolean
onClose: () => void
}
export function AddPaymentMethod({ open, onClose }: AddPaymentMethodProps) {
const [ready, setReady] = useState(false)
const { mutateAsync: createOrganizationBillingCardIntent } =
useCreateOrganizationBillingCardIntent()
const { mutateAsync: confirmCreateOrganizationBillingCardIntent } =
useConfirmCreateOrganizationBillingCardIntent()
const { toast } = useToast()
const {
mutateAsync: submit,
error,
isPending: isSubmitting,
} = useMutation({
mutationFn: async ({
confirm,
}: Parameters<FillPaymentFormProps['handleSubmit']>[0]) => {
const { clientSecret, intentId } =
await createOrganizationBillingCardIntent()
await confirm({ clientSecret })
await confirmCreateOrganizationBillingCardIntent({
intentId,
})
toast({
title: 'Payment method added',
description: 'Your payment method has been added successfully',
})
onClose()
},
})
const handleSubmit = useCallback(
async ({
confirm,
}: Parameters<FillPaymentFormProps['handleSubmit']>[0]) => {
await submit({ confirm })
},
[submit],
)
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Add payment method</DialogTitle>
<DialogDescription>
Add a new payment method to your account.
</DialogDescription>
</DialogHeader>
<FillPayment mode="setup" currency="usd">
<FillPaymentForm
className="flex flex-col"
onReady={() => setReady(true)}
handleSubmit={handleSubmit}
>
{error && (
<p className="text-center text-red-500">{error.message}</p>
)}
<Button
disabled={!ready || isSubmitting}
type="submit"
className="w-fit self-end"
>
Save Payment Method
</Button>
</FillPaymentForm>
</FillPayment>
</DialogContent>
</Dialog>
)
}
fill payment, the actual form:
'use client'
type SetupMode = {
mode: 'setup'
}
type PaymentMode = {
mode: 'payment'
amount: number
}
type BaseProps = {
children: React.ReactNode
currency: string
}
type FillPaymentProps = BaseProps & (SetupMode | PaymentMode)
export function FillPayment(props: FillPaymentProps) {
const { theme } = useTheme()
const appearance =
theme === 'dark'
? {
variables: {
colorBackground: '#000',
colorText: '#fff',
},
}
: undefined
return (
<Elements
options={{
mode: props.mode,
amount: props.mode === 'payment' ? props.amount : undefined,
currency: props.currency,
paymentMethodTypes: ['card'],
appearance,
}}
stripe={stripePromise}
>
{props.children}
</Elements>
)
}
export type FillPaymentFormProps = {
children: React.ReactNode
className?: string
onReady: () => void
handleSubmit: ({
confirm,
}: {
confirm: ({
clientSecret,
}: {
clientSecret: string
}) => Promise<SetupIntent>
}) => void
}
export function FillPaymentForm({
handleSubmit,
onReady,
className,
children,
}: FillPaymentFormProps) {
const stripe = useStripe()
const elements = useElements()
const submit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
// stripe and elements will be null when submitting
if (!stripe || !elements) return
await elements.submit()
handleSubmit({
confirm: async ({ clientSecret }) => {
try {
const { error, setupIntent } = await stripe.confirmSetup({
clientSecret,
elements,
redirect: 'if_required',
})
if (error) throw error
return setupIntent
} catch (error) {
if (
typeof error === 'object' &&
error &&
'message' in error &&
typeof error.message === 'string'
)
throw new Error(error.message)
throw new Error(
'Unable to confirm your payment method. Try again later or another payment method.',
)
}
},
})
},
[handleSubmit],
)
return (
<form onSubmit={submit} className={cn('space-y-4', className)}>
<PaymentElement onReady={onReady} />
{children}
</form>
)
}
Berger PicardOP
demo
@Berger Picard you might want to head over directly to stripe's server: https://discord.gg/stripe
Berger PicardOP
thanks, didnt even know that existed
Berger PicardOP
found it, the useCallback didnt contain the stripe and elements as a dependency
Answer