antonwilhelm
antonwilhelm

Reputation: 7493

How to Integrate Google reCAPTCHA with react-hook-form?

I have been using react-hook-form before, for users to submit their email address in a basic form:

BEFORE: react-hook-form, no catpcha

import React from 'react'
import { useForm } from 'react-hook-form'
import { useRouter } from 'next/router'

const MyForm = ({ btnText = 'Join' }) => {
  const router = useRouter()

  const {
    register,
    handleSubmit,
    formState: { isSubmitted, isSubmitting, isValid, errors },
    reset,
  } = useForm({
    mode: 'onChange',
    reValidateMode: 'onChange',
  })

  const onSubmit = async ({ email }) => {

    const response = await fetch('/api/my-endpoint', {
        method: 'POST',
        body: JSON.stringify({
          email: email,
          captcha: captchaCode,
        }),
        headers: {
          'Content-Type': 'application/json',
        },
      })
  }


  return (
    <div tw="">
      <form
        onSubmit={handleSubmit(onSubmit)}
      >
      
        <input
          {...register('email', {
            required: 'We need an e-mail address',
          })}
          type="email"
        />

        <button
          type="submit"
        >
          Submit
        </button>
      </form>
    </div>
  )
}

export default MyForm

Now I just added google ReCaptcha v2, but I struggle to understand how to integrate it into react-hoook-form?

NOW: react-hook-form + google recatpcha v2

import React from 'react'
import { useForm } from 'react-hook-form'
import ReCAPTCHA from 'react-google-recaptcha'

const MyForm = ({ btnText = 'Join' }) => {

  const {
    register,
    handleSubmit,
    formState: { isSubmitted, isSubmitting, isValid, errors },
    reset,
  } = useForm({
    mode: 'onChange',
    reValidateMode: 'onChange',
  })

  const onSubmit = ({ email }) => {
    // Execute the reCAPTCHA when the form is submitted
    recaptchaRef.current.execute()
  }

  const onReCAPTCHAChange = async captchaCode => {
    // If the reCAPTCHA code is null or undefined indicating that
    // the reCAPTCHA was expired then return early
    if (!captchaCode) {
      return
    }
    try {
      const response = await fetch('/api/my-endpoint', {
        method: 'POST',
        body: JSON.stringify({
          email: email,
          captcha: captchaCode,
        }),
        headers: {
          'Content-Type': 'application/json',
        },
      })
      if (response.ok) {
        // If the response is ok than show the success alert
        alert('Email registered successfully')
      } else {
        // Else throw an error with the message returned
        // from the API
        const error = await response.json()
        throw new Error(error.message)
      }
    } catch (error) {
      alert(error?.message || 'Something went wrong')
    } finally {
      // Reset the reCAPTCHA when the request has failed or succeeeded
      // so that it can be executed again if user submits another email.
      recaptchaRef.current.reset()

      reset()
    }
  }

  return (
      <form
        onSubmit={handleSubmit(onSubmit)}
      >
        <ReCAPTCHA
          ref={recaptchaRef}
          size="invisible"
          sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
          onChange={onReCAPTCHAChange}
        />
        <input
          {...register('email', {
            required: 'We need an e-mail address',
          })}
          type="email"
        />

        <button
          type="submit"
        >
         Submit
        </button>
      </form>
  )
}

export default MyForm

My Problem:

What I seem to struggle with, is that before I used to do an async handleSubmit call:

const onSubmit = async ({ email }) => {

    const response = await fetch('/api/my-endpoint', {
        method: 'POST',
        body: JSON.stringify({
          email: email,
          captcha: captchaCode,
        }),
        headers: {
          'Content-Type': 'application/json',
        },
      })
  }

Whereas now, onSubmit just activates the captcha:

const onSubmit = ({ email }) => {
    // Execute the reCAPTCHA when the form is submitted
    recaptchaRef.current.execute()
  }

...and my actual request is now only being submitted inside the onReCAPTCHAChange function. There, I don't have access to react-hook-form value of email anymore. How can I get that access there?

Also: my handleSubmit function is now synchronous, so I cannot wait for the API response? How can I make this async, but still work with react-hook-form and recaptcha? Any advice?

Upvotes: 4

Views: 6142

Answers (2)

B14s
B14s

Reputation: 861

You can call your real onSubmit function with the data you need by calling the function returned by handleSubmit:

// inside your reCAPTCHA response function
const onSubmitWithFormValues = handleSubmit(
    // binding form values to your function and any other params (e.g. token)
    (...formSubmitParams) => onSubmit(...formSubmitParams, recaptchaToken)
)

onSubmitWithFormValues()

For the above example, the onSubmit signature is the following:

const onSubmit = ({ email, password, username }, _ /* unused event */, recaptchaToken: string) => { ... }

Upvotes: 1

Muhammad Wasif
Muhammad Wasif

Reputation: 84

useForm provides a getValues() function to get the values of form. You can use it any where inside you component. Here's the reference: https://react-hook-form.com/api/useform/getvalues

 const { getValues } = useForm()
 const onReCAPTCHAChange = async captchaCode => {
    // If the reCAPTCHA code is null or undefined indicating that
    // the reCAPTCHA was expired then return early
    if (!captchaCode) {
      return
    }
    try {
      const values = getValues()
      const response = await fetch('/api/my-endpoint', {
        method: 'POST',
        body: JSON.stringify({
          email: values.email,
          captcha: captchaCode,
        }),
        headers: {
          'Content-Type': 'application/json',
        },
      })
     
    }
    ....
}

Alternatively, you can use executeAsync instead of execute inside your onSubmit of hook form and then execute your request.

const onSubmit = ({ email }) => {
    const token = await recaptchaRef.current.executeAsync();

    // You API call here
}

Upvotes: 2

Related Questions