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

NextJS表单提交后实现数据校验及成功/错误消息展示

NextJS 表单校验+提交结果提示实现方案

核心问题修正

现有代码存在两个明显逻辑问题:

  • 未在发起请求前做字段规则校验,非法数据会直接发到接口端
  • 异步请求未做状态跟踪,且表单重置逻辑位置错误,会不等接口返回就直接清空输入内容,提交失败时用户输入的内容会全部丢失

具体实现步骤

1. 新增状态依赖

引入useState维护三类核心状态,分别存储字段校验错误、提交进度、全局提示信息:

import { useRef, useState } from "react";

export default function Form() {
  const nameRef = useRef();
  const emailRef = useRef();
  const purposeRef = useRef();
  const portfolioRef = useRef();
  const messageRef = useRef();
  const locationRef = useRef();
  const comradeRef = useRef();

  // 字段校验错误
  const [fieldErrors, setFieldErrors] = useState({});
  // 提交状态:idle(待提交)/loading(提交中)/success(成功)/error(失败)
  const [submitStatus, setSubmitStatus] = useState("idle");
  // 全局提示文案
  const [tipMessage, setTipMessage] = useState("");

  // 后续逻辑写在这里
}

2. 实现前端校验逻辑

在收集完表单数据、发起请求前逐字段校验,校验不通过直接阻断请求,展示对应错误提示。基础校验规则可参考:

  • 必填字段:去除首尾空格后不能为空
  • 邮箱字段:符合通用邮箱格式
  • comrade字段:仅支持输入Yes/No(不区分大小写)
  • 作品集链接:选填,填写内容需符合URL格式
// 表单校验函数,返回校验结果和错误信息
function validateForm(formData) {
  const errors = {};
  const emailReg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  const urlReg = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;

  // 必填项校验
  if (!formData.name.trim()) errors.name = "请输入你的称呼";
  if (!formData.email.trim()) {
    errors.email = "请输入邮箱地址";
  } else if (!emailReg.test(formData.email.trim())) {
    errors.email = "请输入有效的邮箱格式";
  }
  if (!formData.purpose) errors.purpose = "请选择联系目的";
  if (!formData.message.trim()) errors.message = "请输入留言内容";
  if (!formData.location.trim()) errors.location = "请输入你的所在地";
  const comradeVal = formData.comrade.trim().toLowerCase();
  if (!comradeVal) {
    errors.comrade = "请填写Yes或No";
  } else if (!["yes", "no"].includes(comradeVal)) {
    errors.comrade = "只能输入Yes或No";
  }
  // 选填项格式校验
  if (formData.portfolio.trim() && !urlReg.test(formData.portfolio.trim())) {
    errors.portfolio = "请输入有效的作品集链接";
  }

  return {
    passed: Object.keys(errors).length === 0,
    errors
  }
}

3. 调整提交函数逻辑

将原有的Promise链式调用改为async/await写法,方便跟踪异步状态、捕获请求异常:

async function submitHandler(event) {
  event.preventDefault();
  // 重置上一次提交的状态
  setFieldErrors({});
  setTipMessage("");
  setSubmitStatus("loading");

  // 收集表单数据
  const reqBody = {
    name: nameRef.current.value,
    email: emailRef.current.value,
    purpose: purposeRef.current.value,
    portfolio: portfolioRef.current.value,
    message: messageRef.current.value,
    location: locationRef.current.value,
    comrade: comradeRef.current.value,
  };

  // 校验不通过直接返回,不发起请求
  const { passed, errors } = validateForm(reqBody);
  if (!passed) {
    setFieldErrors(errors);
    setSubmitStatus("idle");
    return;
  }

  try {
    const res = await fetch("/api/feedback", {
      method: "POST",
      body: JSON.stringify(reqBody),
      headers: {
        "Content-Type": "application/json",
      },
    });
    const data = await res.json();
    // 接口返回非200状态码时抛出错误
    if (!res.ok) throw new Error(data.message || "提交失败,请稍后重试");
    
    // 提交成功处理
    setSubmitStatus("success");
    setTipMessage("提交成功!我们会尽快和你联系");
    event.target.reset();
  } catch (err) {
    // 提交失败处理(包含网络错误、接口返回错误)
    setSubmitStatus("error");
    setTipMessage(err.message);
  }
}

注意:表单重置逻辑移到了请求成功的分支里,避免提交失败时用户输入的内容被清空。

4. 在JSX中渲染提示内容

  • 表单顶部渲染全局提示:加载中、提交成功、提交失败分别展示不同样式的提示
  • 每个输入控件下方渲染对应字段的校验错误
  • 提交过程中禁用按钮,防止重复提交
    示例代码(以姓名字段、提交按钮为例,其余字段参照相同格式添加即可):
return (
  <div className={classes.divForm}>
    <form onSubmit={submitHandler}>
      {/* 全局提示区域 */}
      {submitStatus === "loading" && <p style={{color: "#165DFF", margin: "0 0 1rem"}}>正在提交,请稍候...</p>}
      {submitStatus === "success" && <p style={{color: "#00B42A", margin: "0 0 1rem"}}>{tipMessage}</p>}
      {submitStatus === "error" && <p style={{color: "#F53F3F", margin: "0 0 1rem"}}>{tipMessage}</p>}

      <div className={classes.formInputDiv}>
        <div className={classes.label}>
          <label htmlFor="name">
            What Do We Call You?:
            <span style={{color: "blue", fontSize: "0.65rem", float: "right"}}>
              *Required
            </span>
          </label>
        </div>
        <input
          required
          className={classes.input}
          type="text"
          id="name"
          name="name"
          placeholder="Enter Your Name"
          ref={nameRef}
        />
        {/* 字段错误提示 */}
        {fieldErrors.name && <p style={{color: "#F53F3F", fontSize: "0.8rem", margin: "0.25rem 0 0"}}>{fieldErrors.name}</p>}
      </div>

      {/* 其余email、purpose、message等字段,按照上面姓名字段的格式,在对应输入控件下方添加错误提示即可 */}

      <div className={classes.buttonDiv}>
        <button 
          className={classes.formButton} 
          type="submit" 
          disabled={submitStatus === "loading"}
        >
          {submitStatus === "loading" ? "提交中..." : "Submit"}
        </button>
      </div>
    </form>
  </div>
);

补充说明

  • 前端校验仅用于优化用户体验,/api/feedback接口端必须重复做一次相同规则的校验,禁止信任前端传入的任何参数,防止恶意请求绕过前端校验提交非法数据。
  • 如果后续表单字段增多、校验规则变复杂,可以直接使用react-hook-form这类成熟的表单库,相比手动用useRef收集数据+写校验逻辑能减少大量重复代码。
  • 提示样式可以根据项目现有UI规范调整,不需要拘泥于示例中的内联样式。

内容的提问来源于stack exchange,提问作者NIPPY The Creative God

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 10:27:22