Ilya Nikitin
Ilya Nikitin

Reputation: 33

How to add a google-recaptcha v3 to a functional react component with a form?

I have a ready-made form in React

I'm trying to add a captcha to it but it would seem that with the only correct option the captcha reload infinity loops

I didt think that in such a simple task in React there could be so many problems

import { GoogleReCaptchaProvider, GoogleReCaptcha } from 'react-google-recaptcha-v3'

type Props = {
  onSubmit: (values: AuthRequest) => Promise<AuthResponse>
}

function AuthForm(props: Props) {
  const [token, setToken] = useState('')
  return (
    <div className={cn('container')}>
      <GoogleReCaptchaProvider reCaptchaKey="[key]">
        <Form
          onSubmit={handlers.submit}
          render={({ handleSubmit }) => (
            <form onSubmit={handleSubmit}>
              <FormField name={'email'} />
              <div>
                <FormField name={'password'} />
              </div>
              <GoogleReCaptcha
                onVerify={(token) => {
                  setToken(token)
                }}
              />
              <div>
                <Button type="submit">Submit</Button>
              </div>
            </form>
          )}
        />
      </GoogleReCaptchaProvider>
    </div>
  )
}

export { AuthForm }

Upvotes: 2

Views: 7875

Answers (3)

Shadi Golroonia
Shadi Golroonia

Reputation: 11

I solved it by using the function component and use memo and sending the form data to the provider as chide props.

import React from 'react';
import {GoogleReCaptcha, GoogleReCaptchaProvider} from "react-google-recaptcha-v3";

const GetTokenGoogleReCaptcha : FC = (props) => {
    const {sendToken, refresh} = props;
    const [token, setToken] = React.useState("");
    const [refreshReCaptcha, setRefreshReCaptcha] = React.useState(false);

    React.useEffect(() => {
     sendToken(token)
    }, [token])

    React.useEffect(() => {
      if(refresh){
          refreshToken();
      }
    }, [refresh])


    const getTokenFunction  = React.useCallback((token) => {
        setToken(token);
    }, []);

    const refreshToken = () => {
        setRefreshReCaptcha(r => !r);
    }

    return (
        <GoogleReCaptchaProvider reCaptchaKey={"reCaptchaKey"}>
            <GoogleReCaptcha
                onVerify={getTokenFunction}
                refreshReCaptcha={refreshReCaptcha}
            />
        </GoogleReCaptchaProvider>
    );
};
export default React.memo(GetTokenGoogleReCaptcha);

then in form have:

import React from 'react';
import {StringUtilities} from "../../utilities/Utilities";
import {ProfileFormItems} from "../../enums/profileEnum";
import CustomTextInput from "../../components/generalComponents/CustomTextInput";
import GetTokenGoogleReCaptcha from "../../components/customComponents/GetTokenGoogleReCaptcha";


const SignIn = () => {

    const [token, setToken] = React.useState("");
    const [RefreshToken, setRefreshToken] = React.useState(false);


    const handleSubmit = async (e) => {
        console.log('token', token)
        setRefreshToken(true);

        //submit formData
        
    };
   
    
    return (<div className={"sing-in-wrap card"}>
            <div className={"card-header"}>
                <div className={"title"}>
                    <h2>
                        signIn
                    </h2>
                </div>
            </div>
            <form autoComplete="off" onSubmit={handleSubmit}>
                <div className={"card-body"}>
                    <div className={"row"}>
                        <div className={"col-12"}>
                            <div className={"form-group"}>
                                <Input
                                    id={ProfileFormItems.email}
                                    name={ProfileFormItems.email}
                                    value={signInObject.email}
                                    onChange={handleChange(ProfileFormItems.email)}
                                    label={ProfileFormItems.email}
                                    
                                />
                            </div>
                        </div>
                        <div className={"col-12"}>
                            <div className={"form-group "}>
                                <Input
                                    id={ProfileFormItems.password}
                                    name={ProfileFormItems.password}
                                    value={signInObject.password}
                                    onChange={handleChange(ProfileFormItems.password)}
                                    label={ProfileFormItems.password}
                                />
                            </div>
                        </div>
                    </div>
                </div>
                <div className={"card-footer"}>
                    <div className={'form-group d-grid'}>
                        <button type="submit" className={"btn btn-primary"}>
                           signIn"
                        </button>
                    </div>
                </div>
            </form>
         <GetTokenGoogleReCaptcha
            sendToken={(token)=> setToken(token)}
            refresh={RefreshToken}
        />
        </div>);
};

export default React.memo(SignIn);

Upvotes: 0

SkyNT
SkyNT

Reputation: 803

You don't state it but I assume you are using react-google-recaptcha-v3 ?

According to the documentation over at https://www.npmjs.com/package/react-google-recaptcha-v3

// 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

I think the loop happens because the GoogleReCaptcha is causing itself to be rerendered by calling a callback that modifies the state of the parent element constantly. BTW, I tried wrapping GoogleReCaptcha in React.memo(), that didn't work (anyway, that's just optimization, not guaranteed to work, so a poor choice to protect unecessary API calls).

Since I am using class based components, none of the solutions to this type of problem really appealed to me. In the end, I solved this by simply not rendering GoogleReCaptcha after the first callback, something like this:

constructor(props) {
    super(props);
    this.state = {
        ...
        reCaptchaToken: null
    }
}

...

<GoogleReCaptchaProvider reCaptchaKey="[key]">
    ...
    { this.state.reCaptchaToken===null ?
        <GoogleReCaptcha
            onVerify={(reCaptchaToken) => {
                this.setState({reCaptchaToken:reCaptchaToken})
            }}
        />
    :
        ''
    }
    ...
</GoogleReCaptchaProvider>

Upvotes: 0

David
David

Reputation: 39

I solved it like this

    import { GoogleReCaptchaProvider, GoogleReCaptcha } from 'react-google-recaptcha-v3'

    type Props = {
      onSubmit: (values: AuthRequest) => Promise<AuthResponse>
    }

    function AuthForm(props: Props) {
      const [token, setToken] = useState('')
      const verifyRecaptchaCallback = React.useCallback((token) => {
        setToken(token)
      }, []);
      return (
        <div className={cn('container')}>
          <GoogleReCaptchaProvider reCaptchaKey="[key]">
            <Form
              onSubmit={handlers.submit}
              render={({ handleSubmit }) => (
                <form onSubmit={handleSubmit}>
                  <FormField name={'email'} />
                  <div>
                    <FormField name={'password'} />
                  </div>
                  <GoogleReCaptcha
                    onVerify={verifyRecaptchaCallback}
                  />
                  <div>
                    <Button type="submit">Submit</Button>
                  </div>
                </form>
              )}
            />
          </GoogleReCaptchaProvider>
        </div>
      )
    }

    export { AuthForm }

Upvotes: 3

Related Questions