reCAPTCHA v3 error: Invalid listener argument

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.

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);
            }
        });
    });
};

Leave a Comment