Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stabilize onVerify callback to prevent infinite loop #185

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<GoogleReCaptcha
onVerify={token => {
setToken(token);
}}
/>
</div>
);
};
```

```javascript
// Example of refreshReCaptcha option:

Expand Down
22 changes: 18 additions & 4 deletions src/google-recaptcha.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;

Expand All @@ -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;

Expand Down
25 changes: 25 additions & 0 deletions src/use-stable-callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useRef, useEffect, useState } from 'react';

type AnyFunc = (...args: any[]) => any | undefined;

const useStableCallback = <T extends AnyFunc>(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 };