diff --git a/README.md b/README.md index 7ac89db..c6b6e99 100644 --- a/README.md +++ b/README.md @@ -100,28 +100,6 @@ ReactDom.render( ); ``` -```javascript -// IMPORTANT NOTES: The `GoogleReCaptcha` component is a wrapper around `useGoogleRecaptcha` hook and use `useEffect` to run the verification. -// It's important that you understand how React hooks work to use it properly. -// Avoid using inline function for the `onVerify` props as it can possibly cause the verify function to run continously. -// To avoid that problem, you can use a memoized function provided by `React.useCallback` or a class method -// The code below is an example that inline function can result in an infinite loop and the verify function runs continously: - -const MyComponent: FC = () => { - const [token, setToken] = useState(); - - return ( -
- { - setToken(token); - }} - /> -
- ); -}; -``` - ```javascript // Example of refreshReCaptcha option: diff --git a/src/google-recaptcha.tsx b/src/google-recaptcha.tsx index ca43eae..4185cd0 100644 --- a/src/google-recaptcha.tsx +++ b/src/google-recaptcha.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; import { useGoogleReCaptcha } from './use-google-recaptcha'; +import { useStableCallback } from './use-stable-callback'; import { logWarningMessage } from './utils'; export interface IGoogleRecaptchaProps { @@ -11,10 +12,17 @@ export interface IGoogleRecaptchaProps { export function GoogleReCaptcha({ action, onVerify, - refreshReCaptcha, + refreshReCaptcha }: IGoogleRecaptchaProps) { const googleRecaptchaContextValue = useGoogleReCaptcha(); + const hasVerify = !!onVerify; + + // handleVerify is a stable reference + // and therefore will not trip the useEffect into an infinite loop + // when onVerify is an anonymous or otherwise changing function. + const handleVerify = useStableCallback(onVerify); + useEffect(() => { const { executeRecaptcha } = googleRecaptchaContextValue; @@ -25,17 +33,23 @@ export function GoogleReCaptcha({ const handleExecuteRecaptcha = async () => { const token = await executeRecaptcha(action); - if (!onVerify) { + if (!hasVerify) { logWarningMessage('Please define an onVerify function'); return; } - onVerify(token); + handleVerify(token); }; handleExecuteRecaptcha(); - }, [action, onVerify, refreshReCaptcha, googleRecaptchaContextValue]); + }, [ + action, + handleVerify, + hasVerify, + refreshReCaptcha, + googleRecaptchaContextValue + ]); const { container } = googleRecaptchaContextValue; diff --git a/src/use-stable-callback.ts b/src/use-stable-callback.ts new file mode 100644 index 0000000..22f0e47 --- /dev/null +++ b/src/use-stable-callback.ts @@ -0,0 +1,25 @@ +import { useRef, useEffect, useState } from 'react'; + +type AnyFunc = (...args: any[]) => any | undefined; + +const useStableCallback = (fn: T): T => { + const ref = useRef(fn); + + useEffect(() => { + ref.current = fn; + }, [fn]); + + const [stableFn] = useState(() => { + const newFn = (...args: any[]) => { + if (ref.current) { + return ref.current(...args); + } + }; + + return newFn as T; + }); + + return stableFn; +}; + +export { useStableCallback };