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

React Hook Form提交前结合Zod校验MongoDB中recipeId唯一性的最佳实践

前端预校验MongoDB唯一性(React Hook Form + Zod)最佳实践

核心思路

前端通过调用后端提供的唯一性检查接口,结合Zod的异步验证能力,在表单提交前完成校验,并将错误信息以Zod原生提示的形式反馈给用户。同时保留后端的唯一性校验作为兜底(防止前端校验被绕过)。

步骤1:后端提供唯一性检查接口

首先需要在后端实现一个接口,用于查询指定recipeId是否已存在。示例接口逻辑(以Express为例):

// 后端路由:/api/check-recipe-id-unique
app.get('/api/check-recipe-id-unique', async (req, res) => {
  const { id } = req.query;
  if (!id) return res.json({ isUnique: false });

  const exists = await Recipe.findOne({ recipeId: id });
  res.json({ isUnique: !exists });
});

步骤2:编写异步Zod验证器

在前端实现一个异步函数,调用上述接口,并通过Zod的refine方法将其绑定到recipeId字段:

import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

// 防抖处理:避免用户输入时频繁发请求(可选但推荐)
const debounce = <T extends (...args: any[]) => Promise<any>>(fn: T, delay: number) => {
  let timeoutId: NodeJS.Timeout;
  return (...args: Parameters<T>) => {
    clearTimeout(timeoutId);
    return new Promise<ReturnType<T>>((resolve) => {
      timeoutId = setTimeout(async () => {
        const result = await fn(...args);
        resolve(result);
      }, delay);
    });
  };
};

// 调用后端接口检查唯一性
const checkRecipeIdUnique = async (recipeId: string): Promise<boolean> => {
  const response = await fetch(`/api/check-recipe-id-unique?id=${encodeURIComponent(recipeId)}`);
  const data = await response.json();
  return data.isUnique;
};

// 给检查函数加防抖
const debouncedCheck = debounce(checkRecipeIdUnique, 500);

// 修改Zod Schema,添加异步验证
const FormSchema = z.object({
  recipeId: z.string().refine(
    async (id) => await debouncedCheck(id),
    { message: "该recipeId已被占用" }
  ),
  name: z
    .string()
    .min(4, { message: "名称长度至少为4个字符" }),
  description: z.string(),
});

步骤3:配置React Hook Form支持异步验证

在初始化useForm时,指定Zod解析器,并设置验证触发时机(按需选择):

const {
  register,
  handleSubmit,
  formState: { errors, isSubmitting }
} = useForm<z.infer<typeof FormSchema>>({
  resolver: zodResolver(FormSchema),
  mode: "onBlur", // 可选:onChange(输入时实时校验)/ onSubmit(提交时校验)
});

const onSubmit = async (data: z.infer<typeof FormSchema>) => {
  // 提交逻辑:调用后端创建食谱接口
};

步骤4:渲染表单与错误提示

在表单中绑定字段,并显示Zod返回的错误信息:

<form onSubmit={handleSubmit(onSubmit)}>
  <div>
    <label>Recipe ID</label>
    <input {...register("recipeId")} type="text" />
    {errors.recipeId && <span style={{ color: 'red' }}>{errors.recipeId.message}</span>}
  </div>

  <div>
    <label>名称</label>
    <input {...register("name")} type="text" />
    {errors.name && <span style={{ color: 'red' }}>{errors.name.message}</span>}
  </div>

  <div>
    <label>描述</label>
    <textarea {...register("description")} />
  </div>

  <button type="submit" disabled={isSubmitting}>提交</button>
</form>

关键注意事项

  • 后端兜底校验不可少:前端校验仅优化用户体验,恶意请求可能绕过前端直接调用后端接口,因此必须保留原有的MongoDB Schema唯一性校验(如unique: true或中间件)。
  • 防抖优化:避免用户输入过程中频繁发起请求,减少后端压力。
  • 错误处理:考虑接口请求失败的情况(如网络错误),可在异步验证函数中添加错误捕获,返回友好提示。

内容的提问来源于stack exchange,提问作者lexer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:15:08