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

React Hook Form父组件提交按钮的加载状态处理问题咨询

解决React Hook Form父组件提交按钮的加载状态问题

下面提供三种可行方案,根据你的代码结构和需求选择即可:

方案1:将Form实例提升到父组件(最简洁)

把useForm的初始化逻辑移到父组件,直接获取isSubmitting状态,同时将表单所需的register方法传给子组件渲染字段。

父组件修改后代码:

import { useForm } from 'react-hook-form';
import FormEditPassword from './FormEditPassword';

const ForceEditPassword = () => {
  // 在父组件初始化Form实例
  const { register, handleSubmit, formState: { isSubmitting } } = useForm();

  // 提交逻辑写在父组件
  const onSubmit = async (formData) => {
    try {
      // 替换为你的实际API调用
      await fetch('/api/update-password', {
        method: 'POST',
        body: JSON.stringify(formData),
        headers: { 'Content-Type': 'application/json' }
      });
      // 提交成功后的处理,比如弹窗提示、页面跳转
    } catch (error) {
      console.error('提交失败:', error);
      // 错误处理,比如显示错误提示
    }
  };

  return (
    <>
      <div className='force-edit-password d-flex flex-column justify-content-center align-items-center'>
        <div className='inner d-flex justify-items-center align-items-center flex-column'>
          {/* 把register传给子组件渲染表单字段 */}
          <FormEditPassword register={register} />

          <button 
            className='bg-primary rounded bottom-0 start-50 translate-middle-x'
            onClick={handleSubmit(onSubmit)}
            disabled={isSubmitting} // 根据提交状态禁用按钮
          >
            <p className='text-light m-0 p-2 text-capitalize rounded'>
              {/* 根据状态切换按钮文本 */}
              {isSubmitting ? 'Menyimpan...' : 'Simpan'}
            </p>
          </button>
        </div>
      </div>
    </>
  );
};

export default ForceEditPassword;

子组件FormEditPassword代码:

const FormEditPassword = ({ register }) => {
  return (
    <div>
      {/* 用父组件传入的register绑定表单字段 */}
      <input 
        {...register('password', { required: 'Password wajib diisi' })}
        type="password"
        placeholder="Password baru"
      />
      <input 
        {...register('confirmPassword', { required: 'Konfirmasi password wajib diisi' })}
        type="password"
        placeholder="Konfirmasi password"
      />
    </div>
  );
};

export default FormEditPassword;

方案2:通过回调函数传递提交状态(适合子组件保留提交逻辑)

如果提交逻辑必须放在子组件内部,可以通过props给子组件传递回调函数,让子组件在isSubmitting状态变化时通知父组件。

子组件FormEditPassword修改后代码:

import { useForm, useEffect } from 'react-hook-form';

const FormEditPassword = ({ onSubmitStateChange }) => {
  const { register, handleSubmit, formState: { isSubmitting } } = useForm();

  // 监听提交状态变化,实时通知父组件
  useEffect(() => {
    onSubmitStateChange(isSubmitting);
  }, [isSubmitting, onSubmitStateChange]);

  // 子组件内部的提交逻辑
  const onSubmit = async (formData) => {
    try {
      await fetch('/api/update-password', {
        method: 'POST',
        body: JSON.stringify(formData),
        headers: { 'Content-Type': 'application/json' }
      });
    } catch (error) {
      console.error('提交失败:', error);
    }
  };

  // 暴露给父组件调用的提交方法
  const triggerSubmit = handleSubmit(onSubmit);

  return (
    <form>
      <input 
        {...register('password', { required: true })}
        type="password"
        placeholder="Password baru"
      />
      <input 
        {...register('confirmPassword', { required: true })}
        type="password"
        placeholder="Konfirmasi password"
      />
    </form>
  );
};

export default FormEditPassword;

父组件修改后代码:

import { useState, useRef } from 'react';
import FormEditPassword from './FormEditPassword';

const ForceEditPassword = () => {
  const [isLoading, setIsLoading] = useState(false);
  const childRef = useRef(null);

  return (
    <>
      <div className='force-edit-password d-flex flex-column justify-content-center align-items-center'>
        <div className='inner d-flex justify-items-center align-items-center flex-column'>
          <FormEditPassword 
            ref={childRef}
            onSubmitStateChange={setIsLoading} // 传递状态回调
          />

          <button 
            className='bg-primary rounded bottom-0 start-50 translate-middle-x'
            onClick={() => childRef.current?.triggerSubmit()}
            disabled={isLoading}
          >
            <p className='text-light m-0 p-2 text-capitalize rounded'>
              {isLoading ? 'Menyimpan...' : 'Simpan'}
            </p>
          </button>
        </div>
      </div>
    </>
  );
};

export default ForceEditPassword;

方案3:通过Ref暴露提交状态(适合子组件完全封装的场景)

使用forwardRef和useImperativeHandle让父组件访问子组件的isSubmitting状态,需结合useEffect监听状态变化触发重渲染。

子组件代码:

import { useForm, forwardRef, useImperativeHandle } from 'react-hook-form';

// 用forwardRef允许父组件获取子组件Ref
const FormEditPassword = forwardRef((props, ref) => {
  const { register, handleSubmit, formState: { isSubmitting } } = useForm();

  // 暴露给父组件的方法和状态
  useImperativeHandle(ref, () => ({
    triggerSubmit: handleSubmit(async (formData) => {
      try {
        await fetch('/api/update-password', {
          method: 'POST',
          body: JSON.stringify(formData),
          headers: { 'Content-Type': 'application/json' }
        });
      } catch (error) {
        console.error('提交失败:', error);
      }
    }),
    isSubmitting
  }));

  return (
    <form>
      <input 
        {...register('password', { required: true })}
        type="password"
        placeholder="Password baru"
      />
      <input 
        {...register('confirmPassword', { required: true })}
        type="password"
        placeholder="Konfirmasi password"
      />
    </form>
  );
});

export default FormEditPassword;

父组件代码:

import { useState, useRef, useEffect } from 'react';
import FormEditPassword from './FormEditPassword';

const ForceEditPassword = () => {
  const [isLoading, setIsLoading] = useState(false);
  const childRef = useRef(null);

  // 监听子组件的提交状态变化,更新父组件状态
  useEffect(() => {
    const updateLoading = () => {
      setIsLoading(childRef.current?.isSubmitting || false);
    };
    updateLoading();
    // 用定时器轮询状态变化(兜底方案,推荐优先用方案2的回调)
    const interval = setInterval(updateLoading, 100);
    return () => clearInterval(interval);
  }, []);

  return (
    <>
      <div className='force-edit-password d-flex flex-column justify-content-center align-items-center'>
        <div className='inner d-flex justify-items-center align-items-center flex-column'>
          <FormEditPassword ref={childRef} />

          <button 
            className='bg-primary rounded bottom-0 start-50 translate-middle-x'
            onClick={() => childRef.current?.triggerSubmit()}
            disabled={isLoading}
          >
            <p className='text-light m-0 p-2 text-capitalize rounded'>
              {isLoading ? 'Menyimpan...' : 'Simpan'}
            </p>
          </button>
        </div>
      </div>
    </>
  );
};

export default ForceEditPassword;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 11:01:34