You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Google reCAPTCHA v2与react-hook-form集成并处理异步请求?

解决React Hook Form集成Google reCAPTCHA v2的两个核心问题

问题根源梳理

你遇到的两个问题本质是:

  1. 表单值与reCAPTCHA回调的上下文割裂,导致回调无法获取表单数据
  2. 错误地将API请求逻辑从onSubmit移到reCAPTCHA回调,丢失了原有的异步等待能力

下面是两种直接可行的解决方案,优先推荐方案一(更符合React Hook Form的设计逻辑):


方案一:在异步onSubmit中手动触发reCAPTCHA验证

核心思路是保留onSubmit的异步特性,先完成表单验证,再手动触发reCAPTCHA,最后在回调中结合表单数据发起API请求。

完整代码示例

import { useForm } from 'react-hook-form';
import ReCAPTCHA from 'react-google-recaptcha';
import { useRef, useState } from 'react';

const EmailSubscriptionForm = () => {
  const { 
    register, 
    handleSubmit, 
    formState: { errors, isValid },
    getValues
  } = useForm({
    mode: 'onSubmit' // 仅在提交时触发验证,按需调整
  });
  const recaptchaRef = useRef(null);
  const [isSubmitting, setIsSubmitting] = useState(false);

  // 异步onSubmit逻辑:先验证表单,再触发reCAPTCHA
  const onSubmit = async () => {
    if (!isValid) {
      // 手动触发表单验证,确保所有字段符合规则
      const validationPassed = await handleSubmit(() => {})();
      if (!validationPassed) return;
    }

    setIsSubmitting(true);
    const email = getValues('email');

    // 通过Promise包裹reCAPTCHA验证+API请求流程,实现异步等待
    return new Promise(async (resolve, reject) => {
      try {
        // 手动触发reCAPTCHA验证
        recaptchaRef.current?.execute();
        
        // 这里的resolve/reject会在reCAPTCHA回调中调用
        window.recaptchaSubmitContext = {
          email,
          resolve,
          reject
        };
      } catch (err) {
        setIsSubmitting(false);
        reject(err);
      }
    });
  };

  // reCAPTCHA回调:获取token后发起API请求
  const handleRecaptchaChange = async (token) => {
    if (!token || !window.recaptchaSubmitContext) return;

    const { email, resolve, reject } = window.recaptchaSubmitContext;
    delete window.recaptchaSubmitContext;

    try {
      // 发起实际API请求
      const response = await fetch('/api/subscribe-email', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email,
          recaptchaToken: token
        })
      });

      if (!response.ok) throw new Error('订阅请求失败');
      const result = await response.json();
      
      resolve(result);
      // 处理成功逻辑:比如提示用户订阅成功
      alert('订阅成功!');
    } catch (err) {
      reject(err);
      // 处理错误逻辑:比如提示用户重试
      alert(`订阅失败:${err.message}`);
    } finally {
      setIsSubmitting(false);
      // 重置reCAPTCHA,允许下次验证
      recaptchaRef.current?.reset();
    }
  };

  return (
    <form 
      onSubmit={(e) => {
        e.preventDefault();
        onSubmit().catch(err => console.error('提交失败:', err));
      }}
    >
      <div>
        <input
          type="email"
          {...register('email', {
            required: '邮箱不能为空',
            pattern: {
              value: /^\S+@\S+\.\S+$/,
              message: '请输入有效的邮箱地址'
            }
          })}
          placeholder="输入你的邮箱"
          disabled={isSubmitting}
        />
        {errors.email && <span className="error">{errors.email.message}</span>}
      </div>

      {/* 隐藏式reCAPTCHA,也可以用size="normal"显示验证按钮 */}
      <ReCAPTCHA
        ref={recaptchaRef}
        sitekey="YOUR_RECAPTCHA_SITE_KEY"
        onChange={handleRecaptchaChange}
        size="invisible"
      />

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '提交中...' : '订阅'}
      </button>
    </form>
  );
};

export default EmailSubscriptionForm;

方案优势

  1. 保留异步能力:通过Promise包裹reCAPTCHA和API请求流程,onSubmit依然可以等待整个流程完成,便于处理加载状态和结果反馈
  2. 表单数据可控:通过getValues在表单验证通过后获取邮箱值,确保拿到的是合法数据
  3. 逻辑清晰:表单验证、验证码触发、API请求的流程完全可控,符合React Hook Form的设计范式

方案二:用状态存储表单值(简化版)

如果觉得方案一的Promise逻辑稍复杂,可以通过watch实时同步表单值到组件状态,再在reCAPTCHA回调中使用:

import { useForm } from 'react-hook-form';
import ReCAPTCHA from 'react-google-recaptcha';
import { useRef, useState, useEffect } from 'react';

const EmailForm = () => {
  const { register, handleSubmit, watch } = useForm();
  const recaptchaRef = useRef(null);
  const [email, setEmail] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);

  // 实时监听邮箱字段变化,同步到状态
  const emailWatch = watch('email');
  useEffect(() => {
    setEmail(emailWatch);
  }, [emailWatch]);

  // 同步onSubmit:仅触发reCAPTCHA验证
  const onSubmit = async (data) => {
    setIsSubmitting(true);
    recaptchaRef.current?.execute();
  };

  // reCAPTCHA回调:使用状态中的邮箱值发起请求
  const handleRecaptchaChange = async (token) => {
    if (!token) return;

    try {
      const response = await fetch('/api/submit-email', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, recaptchaToken: token })
      });

      if (!response.ok) throw new Error('请求失败');
      alert('提交成功!');
    } catch (err) {
      alert(`提交失败:${err.message}`);
    } finally {
      setIsSubmitting(false);
      recaptchaRef.current?.reset();
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="email"
        {...register('email', { required: '邮箱不能为空', pattern: /^\S+@\S+\.\S+$/ })}
        placeholder="输入邮箱"
        disabled={isSubmitting}
      />

      <ReCAPTCHA
        ref={recaptchaRef}
        sitekey="YOUR_RECAPTCHA_SITE_KEY"
        onChange={handleRecaptchaChange}
        size="invisible"
      />

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '提交中...' : '提交'}
      </button>
    </form>
  );
};

方案说明

  • 适合简单场景,通过watch实时同步表单值,无需手动管理Promise
  • 缺点是watch会频繁触发状态更新,相比方案一稍显冗余

内容的提问来源于stack exchange,提问作者antonwilhelm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 01:31:34