t49tran / react-google-recaptcha-v3

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

[Bug] Can not use more than one provider #106

Closed colorninja closed 2 years ago

colorninja commented 2 years ago

Issue description

When you try to use multiple providers, some of the providers might no receive the execute function because of race condition.

I am using this on pages where react is not a full SPA yet, so it's broken down into components. Therefore I can not use the same provider and am forced to use more than one provider.

Steps to reproduce the issue

  1. Create a simple HOC function like this one:
    function withGoogleRecaptcha<Props = {}>(WrappedComponent: React.ComponentType<Props>): React.ComponentType<Props> {
    return props => <GoogleReCaptchaProvider reCaptchaKey={process.env.RECAPTCHA_SITE_KEY} useRecaptchaNet>
        <WrappedComponent {...props}/>
    </GoogleReCaptchaProvider>;
    }
  2. Map it to all of your components:
    
    interface EmbeddedElements {
    component: React.ComponentType,
    selector: string;
    }

const elements: EmbeddedElements[] = [ {component: withGoogleRecaptcha(Newsletter), selector: '#newsletter-react'}, {component: withGoogleRecaptcha(AnotherFormModal), selector: '.form-modal-react'}, ];

elements.map(({selector, component: Component}) => { const items = document.querySelectorAll(selector);

items.forEach(
    item => ReactDOM.render(<Component {...JSON.parse(item.dataset.props || '{}')}/>, item)
);

});


3. Try to call `executeRecaptcha` from within the AnotherFormModal component
4. You get the following error `TypeError: executeRecaptcha is not a function`

#### What's the expected result?
- Both providers hold the the google execute method context 

#### What's the actual result?
- Only the providers that were already rendered when `grecaptcha.ready()` was called will be correct.

#### Additional details / screenshot
It wouldn't be hard to globally store the execute method (to the window object for example) and automatically assign it to every new provider. However the challenge would be to have multiple providers with the different key as it looks like google does not support that.  I propose that we store the key globally along the execute method and throw an exception if client tries to instantiate two providers with different keys
colorninja commented 2 years ago

Since I am only using one key, I am currently using a ugly workaround, but hey - it works!

import {useGoogleReCaptcha} from "react-google-recaptcha-v3";

export const useRecaptchaWrapper = () => {
    const {executeRecaptcha} = useGoogleReCaptcha();

    const globalRecaptcha = (action: string) => window.grecaptcha?.execute(process.env.RECAPTCHA_SITE_KEY, {action});

    return {
        executeRecaptcha: executeRecaptcha || globalRecaptcha,
    };
}
t49tran commented 2 years ago

Hi @colorninja, the lib is designed to work with SPA. The job of the provider is to inject a script to the site and listen on the onload event of that script to get the execute function. It's built to have one provider instance per page.

I will consider the approach you propose and how this lib can address the situations when there needs to be more than one instance per page, but it's not something I can guarantee that will happen.

himadrinath commented 1 year ago

@t49tran

<root>
<content>
{ Authorize ?
<GoogleReCaptchaProvider reCaptchaKey={process.env.RECAPTCHA_SITE_KEY}>
{children}
</GoogleReCaptchaProvider>
:
<>
{children}
</>
</content>

<Modal>
{ condition &&
<GoogleReCaptchaProvider reCaptchaKey={process.env.RECAPTCHA_SITE_KEY}>
<dynamicComponent/>
</GoogleReCaptchaProvider>
</Modal>

</root>

having same problem

TypeError: executeRecaptcha is not a function