From 05ef16025a890f023f5ccf2273a5e379372c6fc5 Mon Sep 17 00:00:00 2001 From: David Porter Date: Tue, 24 Sep 2024 12:25:53 +1000 Subject: [PATCH] ON-43678 # change to single ref of captchas --- src/OneBlinkFormBase.tsx | 48 ++++++++++--------- .../WestpacQuickStreamPaymentForm.tsx | 4 +- src/form-elements/FormElementCaptcha.tsx | 5 +- src/hooks/useCaptcha.ts | 10 ++-- src/hooks/useReCAPTCHAProps.ts | 2 +- src/services/form-validation.ts | 2 +- src/types/form.ts | 2 +- 7 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/OneBlinkFormBase.tsx b/src/OneBlinkFormBase.tsx index de97f315..d5ff633e 100644 --- a/src/OneBlinkFormBase.tsx +++ b/src/OneBlinkFormBase.tsx @@ -143,7 +143,7 @@ export type OneBlinkFormBaseProps = OneBlinkReadOnlyFormProps & { onUploadAttachment?: typeof attachmentsService.uploadAttachment /** * Determines whether to use checkboxes or invisible recaptcha v2 for captcha - * elements + * elements. Defaults to "CHECKBOX" */ captchaType?: CaptchaType } @@ -197,9 +197,7 @@ function OneBlinkFormBase({ }: Props) { const isOffline = useIsOffline() const { isUsingFormsKey, userProfile } = useAuth() - const [captchaRefs, setCaptchaRefs] = React.useState< - Array> - >([]) + const captchasRef = React.useRef>([]) const theme = React.useMemo( () => @@ -383,7 +381,7 @@ function OneBlinkFormBase({ const { validate } = useFormValidation(pages) const recaptchaType = React.useMemo( - () => captchaType ?? 'CHECKBOXES', + () => captchaType ?? 'CHECKBOX', [captchaType], ) @@ -550,11 +548,23 @@ function OneBlinkFormBase({ [definition], ) - const addCaptchaRef = React.useCallback( - (recaptchaRef: React.RefObject) => { - setCaptchaRefs((current) => [...current, recaptchaRef]) - }, - [], + const addCaptchaRef = React.useCallback((recaptcha: ReCAPTCHA) => { + captchasRef.current.push(recaptcha) + // this allows the FormElementCaptcha element to unregister any captchas + return () => { + captchasRef.current = captchasRef.current.filter( + (recaptchaInstance) => recaptchaInstance === recaptcha, + ) + } + }, []) + + const captchaContextValue = React.useMemo( + () => ({ + captchaSiteKey, + captchaType: recaptchaType, + addCaptchaRef, + }), + [addCaptchaRef, captchaSiteKey, recaptchaType], ) const resetRecaptchas = React.useCallback(() => { @@ -566,14 +576,14 @@ function OneBlinkFormBase({ } }) // reset each captcha - captchaRefs.forEach((ref) => { - ref.current?.reset() + captchasRef.current.forEach((captcha) => { + captcha.reset() }) setHasAttemptedSubmit(false) setFormSubmission((current) => { return { ...current, submission: updatedModel } }) - }, [definition.elements, setFormSubmission, submission, captchaRefs]) + }, [definition.elements, setFormSubmission, submission]) const handleSubmit = React.useCallback( async ( @@ -613,9 +623,9 @@ function OneBlinkFormBase({ } if (captchaType === 'INVISIBLE') { - if (captchaRefs.length) { + if (captchasRef.current.length) { const tokenResults = await Promise.allSettled( - captchaRefs.map((ref) => ref.current?.executeAsync()), + captchasRef.current.map((captcha) => captcha.executeAsync()), ) const captchaTokens: string[] = [] @@ -707,7 +717,6 @@ function OneBlinkFormBase({ userProfile, onSubmit, resetRecaptchas, - captchaRefs, setFormSubmission, ], ) @@ -1056,12 +1065,7 @@ function OneBlinkFormBase({ value={abnLookupAuthenticationGuid} > captchaType ?? 'CHECKBOXES', + () => captchaType ?? 'CHECKBOX', [captchaType], ) @@ -264,7 +264,7 @@ function WestpacQuickStreamPaymentForm({ displayCaptchaRequired: false, } } - case 'CHECKBOXES': + case 'CHECKBOX': default: return { recaptchaToken: captchaToken, displayCaptchaRequired: true } } diff --git a/src/form-elements/FormElementCaptcha.tsx b/src/form-elements/FormElementCaptcha.tsx index 5d5af71c..c3d23c63 100644 --- a/src/form-elements/FormElementCaptcha.tsx +++ b/src/form-elements/FormElementCaptcha.tsx @@ -25,8 +25,9 @@ function FormElementCaptcha({ const captchaRef = React.useRef(null) React.useEffect(() => { - if (captchaRef) { - addCaptchaRef(captchaRef) + if (captchaRef.current) { + // addCaptchaRef returns a function to remove the captcha which will fire when the component unmounts + return addCaptchaRef(captchaRef.current) } }, [captchaRef, addCaptchaRef]) diff --git a/src/hooks/useCaptcha.ts b/src/hooks/useCaptcha.ts index f228b215..c9122aa7 100644 --- a/src/hooks/useCaptcha.ts +++ b/src/hooks/useCaptcha.ts @@ -1,17 +1,15 @@ -import { useContext, createContext, RefObject } from 'react' +import { useContext, createContext } from 'react' import { ReCAPTCHA } from 'react-google-recaptcha' import { CaptchaType } from '../types/form' export const CaptchaContext = createContext<{ captchaSiteKey: string | undefined captchaType: CaptchaType - captchaRefs: Array> - addCaptchaRef: (ref: RefObject) => void + addCaptchaRef: (captcha: ReCAPTCHA) => () => void }>({ captchaSiteKey: undefined, - captchaType: 'CHECKBOXES', - captchaRefs: [], - addCaptchaRef: () => {}, + captchaType: 'CHECKBOX', + addCaptchaRef: () => () => {}, }) export default function useCaptcha() { diff --git a/src/hooks/useReCAPTCHAProps.ts b/src/hooks/useReCAPTCHAProps.ts index e53fc25f..c1a8bd2f 100644 --- a/src/hooks/useReCAPTCHAProps.ts +++ b/src/hooks/useReCAPTCHAProps.ts @@ -26,7 +26,7 @@ export default function useReCAPTCHAProps({ size: 'invisible', badge: 'inline', } - case 'CHECKBOXES': + case 'CHECKBOX': default: { return { ...baseProps, diff --git a/src/services/form-validation.ts b/src/services/form-validation.ts index 51a8a251..c6f2455f 100644 --- a/src/services/form-validation.ts +++ b/src/services/form-validation.ts @@ -336,7 +336,7 @@ export function generateValidationSchema( switch (captchaType) { case 'INVISIBLE': return - case 'CHECKBOXES': + case 'CHECKBOX': default: return { presence: presence( diff --git a/src/types/form.ts b/src/types/form.ts index 58b4461a..997898ef 100644 --- a/src/types/form.ts +++ b/src/types/form.ts @@ -91,4 +91,4 @@ export type IsDirtyProps = { //TODO get from types // export type captchaType = IntegrationTypes.IntegrationRecaptcha['configuration']['domains'][number]['type'] -export type CaptchaType = 'INVISIBLE' | 'CHECKBOXES' +export type CaptchaType = 'INVISIBLE' | 'CHECKBOX'