@@ -6,8 +6,8 @@ import React from 'react';
6
6
import PropTypes from 'prop-types' ;
7
7
8
8
import { 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' ;
11
11
12
12
const INVALID_STRIPE_ERROR =
13
13
'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 => {
23
23
throw new Error ( INVALID_STRIPE_ERROR ) ;
24
24
} ;
25
25
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
-
48
26
interface ElementsContextValue {
49
27
elements : stripeJs . StripeElements | null ;
50
28
stripe : stripeJs . Stripe | null ;
@@ -66,6 +44,14 @@ export const parseElementsContext = (
66
44
return ctx ;
67
45
} ;
68
46
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
+
69
55
interface ElementsProps {
70
56
/**
71
57
* A [Stripe object](https://stripe.com/docs/js/initializing) or a `Promise` resolving to a `Stripe` object.
@@ -99,66 +85,44 @@ interface PrivateElementsProps {
99
85
*
100
86
* @docs https://stripe.com/docs/stripe-js/react#elements-provider
101
87
*/
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 ) {
121
103
console . warn (
122
104
'Unsupported prop change on Elements: You cannot change the `stripe` prop after setting it.'
123
105
) ;
124
106
}
125
- if ( ! isEqual ( options , prevOptions ) ) {
107
+
108
+ if ( hasOptionsChanged && ! canUpdate ) {
126
109
console . warn (
127
110
'Unsupported prop change on Elements: You cannot change the `options` prop after setting the `stripe` prop.'
128
111
) ;
129
112
}
130
- }
131
113
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 ] )
140
117
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 ) ) ;
156
121
122
+ const shouldInitialize = resolvedStripe !== null && ctx . stripe === null
157
123
React . useEffect ( ( ) => {
158
- return ( ) : void => {
159
- isMounted . current = false ;
160
- } ;
161
- } , [ ] ) ;
124
+ if ( shouldInitialize ) setContext ( createElementsContext ( resolvedStripe , inputs . options ) )
125
+ } , [ shouldInitialize , resolvedStripe , inputs . options ] )
162
126
163
127
React . useEffect ( ( ) => {
164
128
const anyStripe : any = ctx . stripe ;
0 commit comments