t49tran / react-google-recaptcha-v3

Google Recaptcha V3 integration for React
MIT License
434 stars 91 forks source link

Error when navigating few times on the page with recaptcha in nextjs website #192

Open Asmedej opened 9 months ago

Asmedej commented 9 months ago

I have a next.js website version 14 (app router) and a react-google-recaptcha-v3.

I've implemented in the contact form component the recommended approach: React Hook: useGoogleReCaptcha (recommended approach).

I have a server side Contact page, very simple

return (
    <>
      <div className='page-title'>
        <h1>Contact</h1>
      </div>
      <div>
        <CaptchaWrapper>
          <ContactForm />
        </CaptchaWrapper>
      </div>
    </> )

CaptchaWrapper component which is client side:

'use client'
import * as React from 'react';
import { GoogleReCaptchaProvider } from "react-google-recaptcha-v3";

interface CaptchaWrapperProps {
    children: React.ReactElement;
}

export default const CaptchaWrapper: React.FunctionComponent<CaptchaWrapperProps> = (props) => {
    const SITE_KEY: string = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? '';
    return (
        <GoogleReCaptchaProvider reCaptchaKey={SITE_KEY} scriptProps={{ async: false, defer: false, appendTo: "head", nonce: undefined, }}>
            {props.children}
        </GoogleReCaptchaProvider>
    );
}

And then the Contact form component which is also client side:

export default const ContactForm: React.FunctionComponent = () => {

    const { executeRecaptcha } = useGoogleReCaptcha();

    useEffect(() => {

        setValid((!!name && !!email && !!subject && !!message));

    },[name, email, subject, message]);

    const handleReCaptchaVerify = useCallback(async () => {
      if (!executeRecaptcha) {
        console.log('Execute recaptcha not yet available');
        return;
      }

      console.log('Token retreival');

      const token = await executeRecaptcha('contactForm');
      setToken(token);

    }, [executeRecaptcha]);

    // You can use useEffect to trigger the verification as soon as the component being loaded
    useEffect(() => {
      console.log('UseEffect');
      handleReCaptchaVerify();
    }, [handleReCaptchaVerify]);

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
      event.preventDefault();

      if(!token) {
        console.log('No token availble');
        return;
      }

      submitEmailSending();

    return (
      <IdPrefixProvider value="cf1-">
        <FluentProvider theme={webDarkTheme} style={{backgroundColor: 'inherit'}}>
          ...form elements...
            <Button appearance="primary" type='submit' disabled={!valid}>Send</Button>
          </form>
        </FluentProvider>
      </IdPrefixProvider>      
    )
  }
    }

Now, iwhen I first time go to contact page, everything works fine, but when I go to another page and go back to contact page few times, then I get an error.

image image

And this error stays there, until I do a reload of the page.

Not sure why it is happening...

The packages I use: "@fluentui/react-components": "^9.43.0", "@formatjs/intl-localematcher": "^0.4.0", "@types/node": "20.4.2", "@types/react": "18.2.15", "@types/react-dom": "18.2.7", "axios": "^1.6.2", "eslint": "8.45.0", "eslint-config-next": "^14.0.4", "jquery": "^3.7.0", "negotiator": "^0.6.3", "next": "^14.0.4", "next-i18next": "^14.0.0", "next-intl": "^2.19.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-google-recaptcha-v3": "^1.10.1", "sass": "^1.64.2", "typescript": "5.1.6"

Asmedej commented 9 months ago

Anyone is still following this? Any idea? Any help? Feeback?

oscar-romero-silva commented 8 months ago

@Asmedej any update on this issue?

pwliuab commented 8 months ago

same, I have experienced this before. Seems the context is not unmounted, which causes null or undefined checking of executeRecaptcha valid. However, the actual js codes are not loaded.

pwliuab commented 8 months ago

i suggest either try catch refresh or wait for a while to reduce the chances of triggering this error: try { await new Promise((resolve, reject) => { setTimeout(() => { resolve("sad"); }, 2000); }); const token = await executeRecaptcha("verifyToken"); setRecaptchaToken(token); } catch (error) { window.location.reload(); }

pwliuab commented 8 months ago

or u can add this line of code before execution: if (!executeRecaptcha) window.grecaptcha = undefined;

pwliuab commented 8 months ago

you could try reset window.grecaptcha to undefined if executeRecaptchais undefined :

    const handleReCaptchaVerify = useCallback(async () => {
        if (!executeRecaptcha) {
            window.grecaptcha = undefined;
            return;
        }
        try {
            const token = await executeRecaptcha("verifyToken");
            setRecaptchaToken(token);
        } catch (error) {
            window.location.reload();
        }

    }, [executeRecaptcha]);
dmorda commented 5 months ago

I've temporarily downgraded to 1.9.7 and that has allowed me to workaround the bug with no code changes until the issue has been fixed.

dmorda commented 5 months ago

I take it back, it makes the error go away, but if you browse off the page and come back and try to submit the form, the captcha validation fails.

pwliuab commented 5 months ago

Please note that you should load this reCAPTCHA component when the page starts loading. If you wrap your UI component with reCAPTCHA and perform lazy loading when the user navigates to the page, a race condition may occur.