@@ -6,8 +6,8 @@ import React from 'react';
66import PropTypes from 'prop-types' ;
77
88import { isEqual } from '../utils/isEqual' ;
9- import { usePrevious } from '../utils/usePrevious ' ;
10- import { isStripe , isPromise } from '../utils/guards' ;
9+ import { usePromiseResolver } from '../utils/usePromiseResolver ' ;
10+ import { isStripe } from '../utils/guards' ;
1111
1212const INVALID_STRIPE_ERROR =
1313 'Invalid prop `stripe` supplied to `Elements`. We recommend using the `loadStripe` utility from `@stripe/stripe-js`. See https://stripe.com/docs/stripe-js/react#elements-props-stripe for details.' ;
@@ -23,28 +23,6 @@ const validateStripe = (maybeStripe: unknown): null | stripeJs.Stripe => {
2323 throw new Error ( INVALID_STRIPE_ERROR ) ;
2424} ;
2525
26- type ParsedStripeProp =
27- | { tag : 'empty' }
28- | { tag : 'sync' ; stripe : stripeJs . Stripe }
29- | { tag : 'async' ; stripePromise : Promise < stripeJs . Stripe | null > } ;
30-
31- const parseStripeProp = ( raw : unknown ) : ParsedStripeProp => {
32- if ( isPromise ( raw ) ) {
33- return {
34- tag : 'async' ,
35- stripePromise : Promise . resolve ( raw ) . then ( validateStripe ) ,
36- } ;
37- }
38-
39- const stripe = validateStripe ( raw ) ;
40-
41- if ( stripe === null ) {
42- return { tag : 'empty' } ;
43- }
44-
45- return { tag : 'sync' , stripe} ;
46- } ;
47-
4826interface ElementsContextValue {
4927 elements : stripeJs . StripeElements | null ;
5028 stripe : stripeJs . Stripe | null ;
@@ -66,6 +44,14 @@ export const parseElementsContext = (
6644 return ctx ;
6745} ;
6846
47+ const createElementsContext = ( stripe : stripeJs . Stripe | null , options ?: stripeJs . StripeElementsOptions ) => {
48+ const elements = stripe ? stripe . elements ( options ) : null
49+ return {
50+ stripe,
51+ elements
52+ }
53+ }
54+
6955interface ElementsProps {
7056 /**
7157 * A [Stripe object](https://stripe.com/docs/js/initializing) or a `Promise` resolving to a `Stripe` object.
@@ -99,66 +85,44 @@ interface PrivateElementsProps {
9985 *
10086 * @docs https://stripe.com/docs/stripe-js/react#elements-provider
10187 */
102- export const Elements : FunctionComponent < ElementsProps > = ( {
103- stripe : rawStripeProp ,
104- options,
105- children,
106- } : PrivateElementsProps ) => {
107- const final = React . useRef ( false ) ;
108- const isMounted = React . useRef ( true ) ;
109- const parsed = React . useMemo ( ( ) => parseStripeProp ( rawStripeProp ) , [
110- rawStripeProp ,
111- ] ) ;
112- const [ ctx , setContext ] = React . useState < ElementsContextValue > ( ( ) => ( {
113- stripe : null ,
114- elements : null ,
115- } ) ) ;
116-
117- const prevStripe = usePrevious ( rawStripeProp ) ;
118- const prevOptions = usePrevious ( options ) ;
119- if ( prevStripe !== null ) {
120- if ( prevStripe !== rawStripeProp ) {
88+ export const Elements : FunctionComponent < ElementsProps > = ( props : PrivateElementsProps ) => {
89+ const { children } = props
90+
91+ if ( props . stripe === undefined ) throw new Error ( INVALID_STRIPE_ERROR ) ;
92+
93+ const [ inputs , setInputs ] = React . useState ( { rawStripe : props . stripe , options : props . options } )
94+ React . useEffect ( ( ) => {
95+ const { rawStripe, options } = inputs
96+ const { stripe : nextRawStripe , options : nextOptions } = props
97+
98+ const canUpdate = rawStripe === null
99+ const hasRawStripeChanged = rawStripe !== nextRawStripe
100+ const hasOptionsChanged = ! isEqual ( options , nextOptions )
101+
102+ if ( hasRawStripeChanged && ! canUpdate ) {
121103 console . warn (
122104 'Unsupported prop change on Elements: You cannot change the `stripe` prop after setting it.'
123105 ) ;
124106 }
125- if ( ! isEqual ( options , prevOptions ) ) {
107+
108+ if ( hasOptionsChanged && ! canUpdate ) {
126109 console . warn (
127110 'Unsupported prop change on Elements: You cannot change the `options` prop after setting the `stripe` prop.'
128111 ) ;
129112 }
130- }
131113
132- if ( ! final . current ) {
133- if ( parsed . tag === 'sync' ) {
134- final . current = true ;
135- setContext ( {
136- stripe : parsed . stripe ,
137- elements : parsed . stripe . elements ( options ) ,
138- } ) ;
139- }
114+ const nextInputs = { rawStripe : nextRawStripe , options : nextOptions }
115+ if ( hasRawStripeChanged && canUpdate ) setInputs ( nextInputs )
116+ } , [ inputs , props ] )
140117
141- if ( parsed . tag === 'async' ) {
142- final . current = true ;
143- parsed . stripePromise . then ( ( stripe ) => {
144- if ( stripe && isMounted . current ) {
145- // Only update Elements context if the component is still mounted
146- // and stripe is not null. We allow stripe to be null to make
147- // handling SSR easier.
148- setContext ( {
149- stripe,
150- elements : stripe . elements ( options ) ,
151- } ) ;
152- }
153- } ) ;
154- }
155- }
118+ const [ maybeStripe = null ] = usePromiseResolver ( inputs . rawStripe )
119+ const resolvedStripe = validateStripe ( maybeStripe )
120+ const [ ctx , setContext ] = React . useState ( ( ) => createElementsContext ( resolvedStripe , inputs . options ) ) ;
156121
122+ const shouldInitialize = resolvedStripe !== null && ctx . stripe === null
157123 React . useEffect ( ( ) => {
158- return ( ) : void => {
159- isMounted . current = false ;
160- } ;
161- } , [ ] ) ;
124+ if ( shouldInitialize ) setContext ( createElementsContext ( resolvedStripe , inputs . options ) )
125+ } , [ shouldInitialize , resolvedStripe , inputs . options ] )
162126
163127 React . useEffect ( ( ) => {
164128 const anyStripe : any = ctx . stripe ;
0 commit comments