0

Trying to implement reCAPTCHA programmatically in a MERN stack app with typescript, I get the error

recaptchaUtils.ts:9  reCAPTCHA error: Error: Invalid listener argument
    at recaptcha__en.js:108:394
    at H2 (recaptcha__en.js:609:438)
    at Object.ready (recaptcha__en.js:389:417)
    at executeRecaptcha (recaptchaUtils.ts:6:1)
    at AuthContext.tsx:112:1
    at new Promise (<anonymous>)
    at login (AuthContext.tsx:110:1)
    at handleLogin (Login.tsx:51:1)
    at onKeyDown (Login.tsx:102:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)

when I try to call this function:

// utils/recaptchaUtils.ts
declare const grecaptcha: any;

export const executeRecaptcha = async (action = 'submit') => {
    if (!grecaptcha || !process.env.REACT_APP_RECAPTCHA_SITE_KEY) {
        console.error("reCAPTCHA library or site key not available");
        return;
    }

    try {
        await grecaptcha.ready();
        return await grecaptcha.execute(process.env.REACT_APP_RECAPTCHA_SITE_KEY, { action });
    } catch (error) {
        console.error("reCAPTCHA error:", error);
    }
};

As you can see, it reaches the try-catch block, but is not successful in executing it.

I was able to think of 2 potential problems, like the site key being incorrect (which I had double checked to be correct both in dev and build versions), and the second problem being race conditions.

However, I am certain that reCAPTCHA is loaded before my executeRecaptcha function is called, because it is loaded like so:

// App.tsx
// ...
declare const grecaptcha: any;

function App() {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = `https://www.google.com/recaptcha/api.js?render=${process.env.REACT_APP_RECAPTCHA_SITE_KEY}`;
    script.async = true;
    script.defer = true;
    script.onload = () => {
      console.log('reCAPTCHA library loaded.');
      grecaptcha.ready(() => {
        console.log('reCAPTCHA is ready.');
      });
    };
    document.head.appendChild(script);
    return () => {
      document.head.removeChild(script);
    };
  }, []);
// ...

and I successfully see reCAPTCHA library loaded. and reCAPTCHA is ready. in the console.

I tried including reCAPTCHA in index.js head, but the same outcome.

I tried without the await, but the same outcome.

I tried without unmounting the reCAPTCHA script, but the same outcome.

I tried testing both in dev and production environments, but the same outcome.

I tried calling executeRecaptcha() from different components, but the same outcome.

I tried reading the reCAPTCHA docs, but did not find anything useful.

2 Answers 2

0

I have fixed the error by changing my executeRecaptcha function to

export const executeRecaptcha = async (action = 'submit'): Promise<string> => {
    return new Promise<string>((resolve, reject) => {
        grecaptcha.ready(() => {
            try {
                grecaptcha.execute(process.env.REACT_APP_RECAPTCHA_SITE_KEY, { action })
                    .then((token: string) => {
                        resolve(token);
                    })
                    .catch((error: any) => {
                        console.error("reCAPTCHA error:", error);
                        reject(error);
                    });
            } catch (error) {
                console.error("reCAPTCHA error:", error);
                reject(error);
            }
        });
    });
};
1
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Nov 8, 2023 at 9:07
0

I was able to solve this on a svelte application with this approach. Apparently async/await is not a pattern that works here. Be also mindful of including the recaptcha script in html. I Hope it helps.

function validateCaptcha(): Promise<string> {
        return new Promise((resolve, reject) => {
            grecaptcha.ready(() => {
                grecaptcha.execute('XXXX', {action: 'submit'})
                    .then(token => {
                        resolve(token);
                    })
                    .catch(error => {
                        console.error("Error executing reCAPTCHA", error);
                        reject(error);
                    });
            });
        });
    }

Not the answer you're looking for? Browse other questions tagged or ask your own question.