Skip to content

Commit 152e5c8

Browse files
committed
Add useCallback and useMemo
1 parent ade858a commit 152e5c8

File tree

4 files changed

+202
-101
lines changed

4 files changed

+202
-101
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import classNames from 'classnames';
2+
import React, { type ReactNode } from 'react';
3+
4+
import { preventDefault } from '@bigcommerce/checkout/dom-utils';
5+
import { TranslatedString } from '@bigcommerce/checkout/locale';
6+
7+
interface EditButtonProps {
8+
buttonId: string | undefined;
9+
shouldShowEditButton: boolean | undefined;
10+
}
11+
12+
export const EditButton = ({ buttonId, shouldShowEditButton }: EditButtonProps): ReactNode => {
13+
if (shouldShowEditButton) {
14+
const translatedString = <TranslatedString id="remote.select_different_card_action" />;
15+
16+
return (
17+
<p>
18+
<button
19+
className={classNames('stepHeader', 'widget-link-amazonpay')}
20+
id={buttonId}
21+
onClick={preventDefault()}
22+
type="button"
23+
>
24+
{translatedString}
25+
</button>
26+
</p>
27+
);
28+
}
29+
30+
return null;
31+
};

packages/hosted-widget-integration/src/HostedWidgetPaymentComponent.tsx

Lines changed: 104 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,18 @@ import {
1010
type PaymentMethod,
1111
type PaymentRequestOptions,
1212
} from '@bigcommerce/checkout-sdk';
13-
import classNames from 'classnames';
1413
import { find, noop } from 'lodash';
1514
import React, {
1615
type ReactElement,
1716
type ReactNode,
1817
useCallback,
1918
useEffect,
19+
useMemo,
2020
useRef,
2121
useState,
2222
} from 'react';
2323
import { type ObjectSchema } from 'yup';
2424

25-
import { preventDefault } from '@bigcommerce/checkout/dom-utils';
2625
import {
2726
AccountInstrumentFieldset,
2827
assertIsCardInstrument,
@@ -31,10 +30,13 @@ import {
3130
isCardInstrument,
3231
StoreInstrumentFieldset,
3332
} from '@bigcommerce/checkout/instrument-utils';
34-
import { TranslatedString } from '@bigcommerce/checkout/locale';
3533
import { type PaymentFormValues } from '@bigcommerce/checkout/payment-integration-api';
3634
import { LoadingOverlay } from '@bigcommerce/checkout/ui';
3735

36+
import { EditButton } from './EditButton';
37+
import { PaymentDescriptor } from './PaymentDescriptor';
38+
import { PaymentWidget } from './PaymentWidget';
39+
3840
export interface PaymentContextProps {
3941
disableSubmit(method: PaymentMethod, disabled?: boolean): void;
4042
setSubmit(method: PaymentMethod, fn: ((values: PaymentFormValues) => void) | null): void;
@@ -175,33 +177,39 @@ const HostedWidgetPaymentComponent = ({
175177
storedCardValidationSchema,
176178
]);
177179

178-
const getSelectedBankAccountInstrument = (
179-
addingNew: boolean,
180-
currentSelectedInstrument: PaymentInstrument,
181-
): AccountInstrument | undefined => {
182-
return !addingNew && isBankAccountInstrument(currentSelectedInstrument)
183-
? currentSelectedInstrument
184-
: undefined;
185-
};
180+
const getSelectedBankAccountInstrument = useCallback(
181+
(
182+
addingNew: boolean,
183+
currentSelectedInstrument: PaymentInstrument,
184+
): AccountInstrument | undefined => {
185+
return !addingNew && isBankAccountInstrument(currentSelectedInstrument)
186+
? currentSelectedInstrument
187+
: undefined;
188+
},
189+
[],
190+
);
186191

187-
const handleDeleteInstrument = (id: string): void => {
188-
if (instruments.length === 0) {
189-
setIsAddingNewCard(true);
190-
setSelectedInstrumentId(undefined);
191-
setFieldValue('instrumentId', '');
192+
const handleDeleteInstrument = useCallback(
193+
(id: string): void => {
194+
if (instruments.length === 0) {
195+
setIsAddingNewCard(true);
196+
setSelectedInstrumentId(undefined);
197+
setFieldValue('instrumentId', '');
192198

193-
return;
194-
}
199+
return;
200+
}
195201

196-
if (selectedInstrumentId === id) {
197-
const nextId = getDefaultInstrumentId();
202+
if (selectedInstrumentId === id) {
203+
const nextId = getDefaultInstrumentId();
198204

199-
setSelectedInstrumentId(nextId);
200-
setFieldValue('instrumentId', nextId);
201-
}
202-
};
205+
setSelectedInstrumentId(nextId);
206+
setFieldValue('instrumentId', nextId);
207+
}
208+
},
209+
[instruments, selectedInstrumentId, getDefaultInstrumentId],
210+
);
203211

204-
const handleUseNewCard = async () => {
212+
const handleUseNewCard = useCallback(async () => {
205213
setIsAddingNewCard(true);
206214
setSelectedInstrumentId(undefined);
207215

@@ -218,14 +226,14 @@ const HostedWidgetPaymentComponent = ({
218226
methodId: method.id,
219227
});
220228
}
221-
};
229+
}, [method, deinitializePayment, initializePayment]);
222230

223-
const handleSelectInstrument = (id: string) => {
231+
const handleSelectInstrument = useCallback((id: string) => {
224232
setIsAddingNewCard(false);
225233
setSelectedInstrumentId(id);
226-
};
234+
}, []);
227235

228-
const getValidateInstrument = (): ReactNode | undefined => {
236+
const getValidateInstrument = useCallback((): ReactNode | undefined => {
229237
const currentSelectedId = selectedInstrumentId || getDefaultInstrumentId();
230238
const currentSelectedInstrument = find(instruments, { bigpayToken: currentSelectedId });
231239

@@ -247,7 +255,14 @@ const HostedWidgetPaymentComponent = ({
247255
}
248256

249257
return undefined;
250-
};
258+
}, [
259+
selectedInstrumentId,
260+
getDefaultInstrumentId,
261+
instruments,
262+
method,
263+
hideVerificationFields,
264+
validateInstrument,
265+
]);
251266

252267
const initializeMethod = async (): Promise<CheckoutSelectors | void> => {
253268
if (!isPaymentDataRequired) {
@@ -291,23 +306,49 @@ const HostedWidgetPaymentComponent = ({
291306
};
292307

293308
// Below values are for lower level components
294-
const effectiveSelectedInstrumentId = selectedInstrumentId || getDefaultInstrumentId();
295-
const selectedInstrument = effectiveSelectedInstrumentId
296-
? instruments.find((i) => i.bigpayToken === effectiveSelectedInstrumentId) || instruments[0]
297-
: instruments[0];
298-
const cardInstruments: CardInstrument[] = instruments.filter(
299-
(i): i is CardInstrument => !isBankAccountInstrument(i),
309+
const effectiveSelectedInstrumentId = useMemo(
310+
() => selectedInstrumentId || getDefaultInstrumentId(),
311+
[selectedInstrumentId, getDefaultInstrumentId],
312+
);
313+
const selectedInstrument = useMemo(
314+
() =>
315+
effectiveSelectedInstrumentId
316+
? instruments.find((i) => i.bigpayToken === effectiveSelectedInstrumentId) ||
317+
instruments[0]
318+
: instruments[0],
319+
[instruments, effectiveSelectedInstrumentId],
320+
);
321+
const cardInstruments: CardInstrument[] = useMemo(
322+
() => instruments.filter((i): i is CardInstrument => !isBankAccountInstrument(i)),
323+
[instruments],
324+
);
325+
const accountInstruments: AccountInstrument[] = useMemo(
326+
() => instruments.filter((i): i is AccountInstrument => isBankAccountInstrument(i)),
327+
[instruments],
300328
);
301-
const accountInstruments: AccountInstrument[] = instruments.filter(
302-
(i): i is AccountInstrument => isBankAccountInstrument(i),
329+
const shouldShowInstrumentFieldset = useMemo(
330+
() => isInstrumentFeatureAvailableProp && instruments.length > 0,
331+
[isInstrumentFeatureAvailableProp, instruments],
332+
);
333+
const shouldShowCreditCardFieldset = useMemo(
334+
() => !shouldShowInstrumentFieldset || isAddingNewCard,
335+
[shouldShowInstrumentFieldset, isAddingNewCard],
336+
);
337+
const isLoading = useMemo(
338+
() => (isInitializing || isLoadingInstruments) && !hideWidget,
339+
[isInitializing, isLoadingInstruments, hideWidget],
340+
);
341+
const selectedAccountInstrument = useMemo(
342+
() =>
343+
selectedInstrument
344+
? getSelectedBankAccountInstrument(isAddingNewCard, selectedInstrument)
345+
: undefined,
346+
[selectedInstrument, isAddingNewCard],
347+
);
348+
const shouldShowAccountInstrument = useMemo(
349+
() => instruments[0] && isBankAccountInstrument(instruments[0]),
350+
[instruments],
303351
);
304-
const shouldShowInstrumentFieldset = isInstrumentFeatureAvailableProp && instruments.length > 0;
305-
const shouldShowCreditCardFieldset = !shouldShowInstrumentFieldset || isAddingNewCard;
306-
const isLoading = (isInitializing || isLoadingInstruments) && !hideWidget;
307-
const selectedAccountInstrument = selectedInstrument
308-
? getSelectedBankAccountInstrument(isAddingNewCard, selectedInstrument)
309-
: undefined;
310-
const shouldShowAccountInstrument = instruments[0] && isBankAccountInstrument(instruments[0]);
311352

312353
useEffect(() => {
313354
const init = async () => {
@@ -399,58 +440,6 @@ const HostedWidgetPaymentComponent = ({
399440
}
400441
}, [selectedInstrumentId, instruments, isPaymentDataRequired]);
401442

402-
const PaymentDescriptor = (): ReactNode => {
403-
if (shouldShowDescriptor && paymentDescriptor) {
404-
return <div className="payment-descriptor">{paymentDescriptor}</div>;
405-
}
406-
407-
return null;
408-
};
409-
410-
const PaymentWidget = (): ReactElement => (
411-
<div
412-
className={classNames(
413-
'widget',
414-
`widget--${method.id}`,
415-
'payment-widget',
416-
shouldRenderCustomInstrument ? '' : additionalContainerClassName,
417-
)}
418-
id={containerId}
419-
style={{
420-
display:
421-
(hideContentWhenSignedOut && isSignInRequired && !isSignedIn) ||
422-
!shouldShowCreditCardFieldset ||
423-
hideWidget
424-
? 'none'
425-
: undefined,
426-
}}
427-
tabIndex={-1}
428-
>
429-
{shouldRenderCustomInstrument && renderCustomPaymentForm && renderCustomPaymentForm()}
430-
</div>
431-
);
432-
433-
const EditButton = (): ReactNode => {
434-
if (shouldShowEditButton) {
435-
const translatedString = <TranslatedString id="remote.select_different_card_action" />;
436-
437-
return (
438-
<p>
439-
<button
440-
className={classNames('stepHeader', 'widget-link-amazonpay')}
441-
id={buttonId}
442-
onClick={preventDefault()}
443-
type="button"
444-
>
445-
{translatedString}
446-
</button>
447-
</p>
448-
);
449-
}
450-
451-
return null;
452-
};
453-
454443
if (!shouldShow) {
455444
return <div style={{ display: 'none' }} />;
456445
}
@@ -478,9 +467,23 @@ const HostedWidgetPaymentComponent = ({
478467
/>
479468
)}
480469

481-
<PaymentDescriptor />
482-
483-
<PaymentWidget />
470+
<PaymentDescriptor
471+
paymentDescriptor={paymentDescriptor}
472+
shouldShowDescriptor={shouldShowDescriptor}
473+
/>
474+
475+
<PaymentWidget
476+
additionalContainerClassName={additionalContainerClassName}
477+
containerId={containerId}
478+
hideContentWhenSignedOut={hideContentWhenSignedOut}
479+
hideWidget={hideWidget}
480+
isSignInRequired={isSignInRequired}
481+
isSignedIn={isSignedIn}
482+
method={method}
483+
renderCustomPaymentForm={renderCustomPaymentForm}
484+
shouldRenderCustomInstrument={shouldRenderCustomInstrument}
485+
shouldShowCreditCardFieldset={shouldShowCreditCardFieldset}
486+
/>
484487

485488
{isInstrumentFeatureAvailableProp && (
486489
<StoreInstrumentFieldset
@@ -492,7 +495,7 @@ const HostedWidgetPaymentComponent = ({
492495
/>
493496
)}
494497

495-
<EditButton />
498+
<EditButton buttonId={buttonId} shouldShowEditButton={shouldShowEditButton} />
496499
</div>
497500
</LoadingOverlay>
498501
);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React, { type ReactNode } from 'react';
2+
3+
interface PaymentDescriptorProps {
4+
paymentDescriptor: string | undefined;
5+
shouldShowDescriptor: boolean | undefined;
6+
}
7+
8+
export const PaymentDescriptor = ({
9+
shouldShowDescriptor,
10+
paymentDescriptor,
11+
}: PaymentDescriptorProps): ReactNode => {
12+
if (shouldShowDescriptor && paymentDescriptor) {
13+
return <div className="payment-descriptor">{paymentDescriptor}</div>;
14+
}
15+
16+
return null;
17+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { type PaymentMethod } from '@bigcommerce/checkout-sdk';
2+
import classNames from 'classnames';
3+
import React, { type ReactElement } from 'react';
4+
5+
interface PaymentWidgetProps {
6+
additionalContainerClassName: string | undefined;
7+
containerId: string;
8+
hideContentWhenSignedOut: boolean;
9+
hideWidget: boolean;
10+
isSignInRequired: boolean | undefined;
11+
isSignedIn: boolean;
12+
method: PaymentMethod;
13+
renderCustomPaymentForm: (() => React.ReactNode) | undefined;
14+
shouldRenderCustomInstrument: boolean;
15+
shouldShowCreditCardFieldset: boolean;
16+
}
17+
18+
export const PaymentWidget = ({
19+
additionalContainerClassName,
20+
containerId,
21+
hideContentWhenSignedOut,
22+
hideWidget,
23+
isSignInRequired,
24+
isSignedIn,
25+
method,
26+
renderCustomPaymentForm,
27+
shouldRenderCustomInstrument,
28+
shouldShowCreditCardFieldset,
29+
}: PaymentWidgetProps): ReactElement => (
30+
<div
31+
className={classNames(
32+
'widget',
33+
`widget--${method.id}`,
34+
'payment-widget',
35+
shouldRenderCustomInstrument ? '' : additionalContainerClassName,
36+
)}
37+
id={containerId}
38+
style={{
39+
display:
40+
(hideContentWhenSignedOut && isSignInRequired && !isSignedIn) ||
41+
!shouldShowCreditCardFieldset ||
42+
hideWidget
43+
? 'none'
44+
: undefined,
45+
}}
46+
tabIndex={-1}
47+
>
48+
{shouldRenderCustomInstrument && renderCustomPaymentForm && renderCustomPaymentForm()}
49+
</div>
50+
);

0 commit comments

Comments
 (0)