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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 09:07:29