Why is react-google-recaptcha-v3 spamming network requests in my ReactJS component?

Viewed 29

Using the package react-google-recaptcha-v3, I am able to get a score for the v3 captcha from google when I submit my form, great! However... If I hope the network tab of chrome I see a neverending loop of requests going out to recaptcha (way before I ever submit the form). Many every second: https://www.google.com/recaptcha/api2/reload?k=xxxx (where xxxx is my recaptcha site key) Is it something from my reactJS component? I can't imagine this is supposed to happen right.

My code is below, I have stripped out the irrelevant content and made the form small for readability.

import React, { useState, useCallback } from 'react'
import config from 'config'
import {
    GoogleReCaptchaProvider,
    GoogleReCaptcha
  } from "react-google-recaptcha-v3"

function ContactForm(props) {
    /*form data*/
    const [name, setName] = useState('')
    /*validation state*/
    const [noNameError, setNoNameError] = useState(false)
    /*recaptcha state*/
    const [token, setToken] = useState();
    const [refreshReCaptcha, setRefreshReCaptcha] = useState(false);
    
    const key = config.RECAPTCHA_V3_SITEKEY

    const onVerify = useCallback((token) => {
        setToken(token);
      });

    const getIP = async()=>{
        const response = await fetch('https://geolocation-db.com/json/');
        const data = await response.json();
        return(data.IPv4)
    }

    const handleSubmit = (event) => {
        event.preventDefault()
        if(!doValidationStuff()){
            setNoNameError(true)
        }
        setNoNameError(false)
        const userIpGetter = getIP()
        userIpGetter.then(function(ipResult){
            myService.doStuff(
                name,
                token,
                ipResult
                )
            .then(()=>{
                doOtherStuff()
                setRefreshReCaptcha(r => !r)
            })
        })
    }
    const setFormName = (event)=>{
        setName(event.target.value)
    }
    
    return (
        <GoogleReCaptchaProvider reCaptchaKey={key}>
            <form id="contactForm" onSubmit={handleSubmit} className="needs-validation">
                <GoogleReCaptcha
                            action='ContactForm'
                            onVerify={onVerify}
                            refreshReCaptcha={refreshReCaptcha}
                        />
                <div className="mb-3">
                    <label className="form-label">Name</label>
                    <input className="form-control" type="text" placeholder="Name" value={name}
                        onChange={setFormName}/>
                    <span style={{ color: "red", display: noNameError ? 'block' : 'none' }}>Please enter your name.</span>
                </div>
                <div className="d-grid">
                    <button className="btn btn-primary btn-lg" type="submit">Submit</button>
                </div>
            </form>
        </GoogleReCaptchaProvider>
    )
}

export { ContactForm };
1 Answers

I ended up having to use the hook from this lib in case anyone else runs into this. Unsure if the refresh is needed in this case, so far I am not doing manual refreshes of the token, leaving that up to the recaptcha magic. Here is the code I ended up with that works, I have stripped out the other parts of the component for readability, but it should still build/run for you:

Way out at the top level of the app:

            <GoogleReCaptchaProvider reCaptchaKey={config.RECAPTCHA_V3_SITEKEY}>
                <App />
            </GoogleReCaptchaProvider>

Then way drilled down into a specific component:

import React, { useState, useEffect, useCallback } from 'react'
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3'

function ContactForm(props) {
    const [isSaving, setIsSaving] = useState(false)
    /*form data*/
    const [name, setName] = useState('')
    /*validation state*/
    const [noNameError, setNoNameError] = useState(false)
    /*recaptcha state*/
    const [recToken, setRecToken] = useState()
    /*START: recaptcha code*/
    const { executeRecaptcha } = useGoogleReCaptcha()
    const handleReCaptchaVerify = useCallback(async () => {
        if (!executeRecaptcha) {
        console.log('Execute recaptcha not yet available');
        return;
        }
        const recTokenResult = await executeRecaptcha('contactForm')
        setRecToken(recTokenResult)
    }, [executeRecaptcha]);
    useEffect(() => {
        handleReCaptchaVerify();
    }, [handleReCaptchaVerify]);
    /*END: recaptcha code*/

    const getIP = async()=>{
        const response = await fetch('https://geolocation-db.com/json/');
        const data = await response.json();
        return(data.IPv4)
    }

    const handleSubmit = (event) => {
        event.preventDefault()
        /*validation start*/
        if(!name || name.length < 3){
            setNoNameError(true)
            return
        }
        else{
            setNoNameError(false)
        }
        /*validation end*/
        const userIpGetter = getIP()
        handleReCaptchaVerify().then(function(){
            userIpGetter.then(function(ipResult){
                blahService.sendContactForm(
                    name,
                    recToken,
                    ipResult
                    )
                .then(()=>{
                    blahService.success('Thank you!')
                })
            })
        })
    }
    const setFormName = (event)=>{
        setName(event.target.value)
    }
    return (
        <form id="contactForm" onSubmit={handleSubmit} className="needs-validation">
            <div className="mb-3">
                <label className="form-label">Name</label>
                <input className="form-control" type="text" placeholder="Name" value={name}
                    onChange={setFormName}/>
                <span style={{ color: "red", display: noNameError ? 'block' : 'none' }}>Please enter your name.</span>
            </div>
            <div className="d-grid">
                <button className="btn btn-primary btn-lg" type="submit">Submit</button>
            </div>
        </form>
    )
}
export { ContactForm };
Related