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
相关产品推荐
相关产品推荐

