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

React Hook Form+Chakra-UI弹窗:表单与按钮交互失效问题排查

问题描述

我想用Chakra-UI做一个通用弹窗,能传入任意表单,表单放在弹窗主体、按钮放在弹窗页脚。页面渲染正常无报错,但提交和错误校验完全没反应。

目标结构如下:

function PopupForm({ type = 'Delete', isOpen, onClose }) {
    return (
        <Modal isOpen={isOpen} onClose={onClose}>
            <ModalOverlay />
            <ModalContent>
                <ModalHeader>{type}</ModalHeader>
                <ModalCloseButton />
                <ModalBody>
                    // <- form body, based on `type`
                </ModalBody>
                <ModalFooter>
                    // <- form buttons, based on `type`
                </ModalFooter>
            </ModalContent>
        </Modal>
    );
}
  
export default PopupForm;

暂时忽略通用性,我手动加入简单表单测试,使用react-hook-form的useFormContext:

import DownloadEppnImForm, { DownloadEppnImFormButtons } from 'components/forms/DownloadEppnImForm';

function PopupForm({ id, isOpen, onClose }) {
    const methods = useForm();

    return (
        <FormProvider {...methods}>
            ...
                    <ModalBody>
                        <DownloadEppnImForm />
                    </ModalBody>
                    <ModalFooter>
                        <DownloadEppnImFormButtons />
                    </ModalFooter>
            ...
        </FormProvider>
    );
}
  
export default PopupForm;

表单组件代码如下:

export const ConnectForm = ({ children }) => {
    const methods = useFormContext();

    return children({ ...methods });
}

export const onSubmit = (values) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Boo!')
            resolve()
        }, 3000)
    })
}
export const DownloadEppnImFormButtons = () => (
    <ConnectForm>
        {({handleSubmit, isLoading, isSubmitting}) => (
            <Button isLoading={isLoading || isSubmitting} onClick={handleSubmit(onSubmit)}>
                Submit
            </Button>
        )}
    </ConnectForm>
);

export default function DownloadEppnImForm () {
    return (
        <ConnectForm>
            {({errors, register}) => (
                <form>
                    <FormControl isInvalid={errors?.name}>
                        <FormLabel htmlFor='name'>First name</FormLabel>
                        <Input
                            id='name'
                            placeholder='name'
                            {...register('name', {
                                required: 'This is required',
                                minLength: { value: 4, message: 'Minimum length should be 4' },
                            })}
                        />
                        <FormErrorMessage>
                            {errors?.name && errors.name.message}
                        </FormErrorMessage>
                    </FormControl>
                </form>
            )}
        </ConnectForm>
    )
}

现在输入“Bob”点击提交,既不显示最小长度错误提示,控制台也无提交日志。参考相关问答及react-hook-form高级用法示例实现ConnectForm包装器仍未解决问题,请问哪里出错了?为何表单和按钮无法交互?


问题分析与解决

核心有两个问题导致表单无响应:

1. 提交按钮未正确触发表单校验与提交流程

  • 当前按钮用onClick直接调用handleSubmit(onSubmit),但react-hook-form的handleSubmit需要绑定到表单的onSubmit事件,或者手动触发校验逻辑,直接调用不会自动执行校验。
  • 按钮不在<form>标签内部,无法通过默认表单提交机制触发校验。

2. useForm未配置校验触发模式

默认情况下react-hook-form的校验是延迟触发的,需要显式配置mode参数指定校验时机(如提交时、输入改变时)。

修复步骤:

步骤1:配置useForm的校验模式

修改PopupForm中的useForm初始化,开启提交时校验:

function PopupForm({ id, isOpen, onClose }) {
    // 添加mode: 'onSubmit',确保提交时触发所有字段校验
    const methods = useForm({ mode: 'onSubmit' });

    return (
        <FormProvider {...methods}>
            {/* 原有Modal代码 */}
        </FormProvider>
    );
}

步骤2:调整按钮与表单的交互逻辑

有两种可选方案:

方案A:将按钮整合到表单内部

修改DownloadEppnImForm,把按钮放到<form>标签内并使用type="submit":

export default function DownloadEppnImForm () {
    return (
        <ConnectForm>
            {({errors, register, handleSubmit, isLoading, isSubmitting}) => (
                <form onSubmit={handleSubmit(onSubmit)}>
                    <FormControl isInvalid={errors?.name}>
                        <FormLabel htmlFor='name'>First name</FormLabel>
                        <Input
                            id='name'
                            placeholder='name'
                            {...register('name', {
                                required: 'This is required',
                                minLength: { value: 4, message: 'Minimum length should be 4' },
                            })}
                        />
                        <FormErrorMessage>
                            {errors?.name && errors.name.message}
                        </FormErrorMessage>
                    </FormControl>
                    {/* 提交按钮移到form内部 */}
                    <Button 
                        mt={4}
                        type="submit" 
                        isLoading={isLoading || isSubmitting}
                    >
                        Submit
                    </Button>
                </form>
            )}
        </ConnectForm>
    )
}

此时可删除单独的DownloadEppnImFormButtons组件,弹窗的ModalFooter可保留取消按钮等其他操作。

方案B:保持按钮在ModalFooter,手动触发校验

若必须将按钮放在弹窗页脚,可通过useFormContext获取trigger方法手动校验字段:

export const DownloadEppnImFormButtons = () => (
    <ConnectForm>
        {({handleSubmit, trigger, isLoading, isSubmitting}) => {
            const handleClick = async () => {
                // 手动触发所有字段校验
                const isValid = await trigger();
                if (isValid) {
                    // 校验通过后执行提交逻辑
                    await handleSubmit(onSubmit)();
                }
            }
            return (
                <Button 
                    isLoading={isLoading || isSubmitting} 
                    onClick={handleClick}
                >
                    Submit
                </Button>
            )
        }}
    </ConnectForm>
);

步骤3:简化ConnectForm组件(可选)

当前的ConnectForm可以简化,无需解构再合并上下文:

export const ConnectForm = ({ children }) => {
    const methods = useFormContext();
    return children(methods);
}

完成以上修改后,点击提交按钮会触发校验逻辑:输入“Bob”时会显示最小长度错误,校验通过后会执行onSubmit并打印日志。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 23:08:22