react-hook-form多步骤表单点击下一步意外触发submit事件问题
多步骤表单意外触发submit事件的原因及解决方案分析
问题背景
我用react-hook-form构建了一个三步多步骤表单,遇到以下问题:
点击第二步的「下一步」按钮切换到第三步(此时isLastStep变为true),按钮类型从button切换为submit,意外触发了表单的submit事件,直接调用了mutation接口。即使将单个按钮拆分为两个分别渲染,问题仍存在。改用onMouseDown替代onClick后问题解决,想了解原因及该方案是否合理。
原表单JSX代码
<form onSubmit={handleSubmit(onSubmit, onInvalidSubmit)} className="flex h-full w-full flex-col justify-between" > <AnimatePresence mode="wait">{<step.content />}</AnimatePresence> <div className="flex w-full items-center justify-between"> <div className=""> <Button onClick={back} variant="ghost" type="button" className={`${isFirstStep ? "invisible" : "visible underline"}`} > Poprzedni krok </Button> </div> <div className="flex items-center"> <div className="relative mt-4"> <Button disabled={!step.canStep?.()} arrow={isLastStep ? undefined : "right"} gradient={true} type={isLastStep ? "submit" : "button"} onClick={() => !isLastStep && next()} className="hover:bg-initial" > {isLastStep && mutationCreateTeacherAcccount.isLoading && ( <Loader2 className="animate-spin" /> )} {isLastStep ? "Utwórz konto" : "Następny krok"} </Button> </div> </div> </div> </form>
onSubmit回调代码
function onSubmit(data: RegisterTeacherSchemaType) { if (isLastStep) { mutationCreateTeacherAcccount.mutate(data); } }
拆分按钮后的代码
{ isLastStep ? ( <Button disabled={!step.canStep?.()} gradient={true} type="submit" className="hover:bg-initial" > {mutationCreateTeacherAcccount.isLoading && ( <Loader2 className="animate-spin" /> )} {"Utwórz konto"} </Button> ) : ( <Button disabled={!step.canStep?.()} arrow="right" gradient={true} type="button" onClick={() => next()} className="hover:bg-initial" > Następny krok </Button> ); }
意外触发submit的原因
这是浏览器事件生命周期+React状态更新后的DOM替换共同导致的:
- 浏览器的点击事件分为三个阶段:
mousedown→mouseup→click。当你点击「下一步」按钮(此时是type="button"),onClick触发next(),React立即更新状态,isLastStep变为true,组件重新渲染,原来的button被卸载,替换成type="submit"的按钮。 - 此时鼠标还处于按下状态,松开时触发的
mouseup和click事件,会落到已经替换完成的submit按钮上。而submit按钮的click事件默认会触发表单的submit行为,因此意外调用了onSubmit里的mutation接口。 - 拆分按钮后,本质还是状态更新导致DOM元素替换,鼠标松开时的事件依然会触发新submit按钮的默认行为,所以问题无法解决。
用onMouseDown替代onClick的合理性
这个方案是可行的,核心原因如下:
mousedown事件在鼠标按下瞬间触发,此时状态还未更新,DOM上仍是原来的type="button"按钮,触发next()完成状态切换后,后续的mouseup和click事件即使落到新的submit按钮上,也不会再触发步骤切换逻辑,且不会触发意外的submit(因为mousedown已经完成了状态更新,后续事件不会关联到步骤切换的逻辑)。- 唯一的小瑕疵:如果用户按下鼠标后拖动到按钮外再松开,
onMouseDown已经触发了步骤切换,这和常规点击交互(只有松开在按钮上才触发)略有差异,但绝大多数场景下用户不会这么操作,实际影响很小。
更稳妥的替代方案
如果想要完全贴合常规点击交互逻辑,还可以尝试以下几种方法:
- 保持按钮类型统一为button:所有步骤的按钮都用
type="button",第三步的按钮点击时手动调用handleSubmit(onSubmit)()来触发表单提交,完全规避浏览器默认的submit行为。 - 延迟状态更新:在
next()中用setTimeout包裹状态更新逻辑,让浏览器先完成当前的点击事件周期,再更新状态渲染submit按钮。 - 临时阻止submit默认行为:给第三步的submit按钮添加一个首次渲染的标记(比如用React ref),首次点击时阻止默认行为,之后移除该逻辑,避免意外触发。
内容的提问来源于stack exchange,提问作者Korer
相关产品推荐
相关产品推荐

