如何在NextJS服务器动作pending时阻止回车键提交表单?
解决Next.js Server Action pending时阻止回车键重复提交表单的最佳实践
针对你遇到的问题——用useFormStatus禁用提交按钮后,用户仍可通过回车键重复提交表单,以下是两种实用的最佳解决方案:
方案一:结合useFormStatus拦截回车键
利用React提供的useFormStatus Hook获取表单提交状态,在表单的onKeyDown事件中拦截回车键的默认行为,和禁用提交按钮形成双重保障:
'use client'; import { useFormStatus } from 'react-dom'; // 封装带防重复提交逻辑的表单容器 function FormWithDuplicateProtection({ children, onSubmit }: { children: React.ReactNode; onSubmit: () => void }) { const { pending } = useFormStatus(); const handleKeyDown = (e: React.KeyboardEvent<HTMLFormElement>) => { // 提交中且按下回车键时,阻止默认提交 if (pending && e.key === 'Enter') { e.preventDefault(); } }; return ( <form onSubmit={onSubmit} onKeyDown={handleKeyDown}> {children} </form> ); } export default function Page() { async function createInvoice(formData: FormData) { 'use server'; const rawFormData = { customerId: formData.get('customerId'), amount: formData.get('amount'), status: formData.get('status'), }; // 模拟数据库插入等异步操作 await new Promise(resolve => setTimeout(resolve, 2000)); // 数据变更与缓存重新验证逻辑 } return ( <FormWithDuplicateProtection onSubmit={createInvoice}> <input type="text" name="customerId" placeholder="客户ID" /> <input type="number" name="amount" placeholder="金额" /> <select name="status"> <option value="pending">待处理</option> <option value="paid">已支付</option> </select> <SubmitButton /> </FormWithDuplicateProtection> ); } // 原提交按钮组件 function SubmitButton() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? '提交中...' : '创建账单'} </button> ); }
这种方案贴合Next.js官方推荐的表单状态管理方式,无需手动维护提交状态,逻辑简洁可靠。
方案二:手动维护提交状态(useRef)
如果需要更灵活的状态控制,可以用useRef追踪提交状态,在Server Action和按键事件中双重校验:
'use client'; import { useRef } from 'react'; export default function Page() { const isSubmitting = useRef(false); async function createInvoice(formData: FormData) { // 若正在提交,直接终止执行 if (isSubmitting.current) return; isSubmitting.current = true; try { 'use server'; const rawFormData = { customerId: formData.get('customerId'), amount: formData.get('amount'), status: formData.get('status'), }; // 模拟异步操作 await new Promise(resolve => setTimeout(resolve, 2000)); // 数据变更与缓存重新验证逻辑 } finally { // 无论成功失败,重置提交状态 isSubmitting.current = false; } } const handleKeyDown = (e: React.KeyboardEvent<HTMLFormElement>) => { if (isSubmitting.current && e.key === 'Enter') { e.preventDefault(); } }; return ( <form action={createInvoice} onKeyDown={handleKeyDown}> <input type="text" name="customerId" placeholder="客户ID" /> <input type="number" name="amount" placeholder="金额" /> <select name="status"> <option value="pending">待处理</option> <option value="paid">已支付</option> </select> <button type="submit" disabled={isSubmitting.current}> {isSubmitting.current ? '提交中...' : '创建账单'} </button> </form> ); }
这种方案无需额外封装组件,适合简单表单场景,能直接在页面内完成状态控制。
关于其他方案的说明
- 防抖:不适合表单提交场景,防抖会延迟提交触发时间,影响用户体验,且无法彻底阻止短时间内的重复回车键提交。
- useTransition:该Hook主要用于优化UI渲染(避免页面卡顿),核心作用是标记非紧急更新,不能直接阻止重复提交,因此不是解决该问题的合适选择。
内容的提问来源于stack exchange,提问作者luciano5656
相关产品推荐
相关产品推荐

