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

如何单独验证Zod Schema中的单个字段(NextJS场景)

解决方案

核心思路

不用给每个字段单独创建Schema,复用现有的createSchema就能实现字段级的onBlur验证,同时优化错误状态的更新逻辑,让修改后的合法字段自动清除错误提示。

步骤1:优化错误状态管理

确保错误状态是可单独更新的对象,用useState初始化:

import { useState } from 'react';

const [clientErrorMessage, setClientErrorMessage] = useState<Record<string, string[] | undefined>>({});

步骤2:创建单个字段验证函数

从主Schema中提取对应字段的验证规则,避免重复定义:

import { z } from 'zod';

// 假设你的主Schema是这样的(已有可跳过)
const createSchema = z.object({
  client_id: z.string().nonempty('请选择客户'),
  hours: z.number().min(10, 'Hours should be between 10 and 100').max(100, 'Hours should be between 10 and 100'),
  hours_left: z.number(),
  price: z.number().positive('价格必须为正数'),
  ends_at: z.string().nonempty('请选择截止日期'),
});

// 单个字段验证函数
const validateSingleField = (fieldName: keyof typeof createSchema.shape, value: unknown) => {
  const fieldSchema = createSchema.shape[fieldName];
  const result = fieldSchema.safeParse(value);
  return result.success ? null : result.error.issues[0].message;
};

步骤3:给输入字段添加onBlur事件

在hours和price的输入框上绑定onBlur,触发单独验证并更新错误状态:

修改Hours字段:

<div className='mb-4'>
  <Label htmlFor='hours' className='mb-2'>
    Hours
  </Label>
  <Input
    type='number'
    name='hours'
    id='hours'
    required
    onBlur={(e) => {
      const value = Number(e.target.value);
      const error = validateSingleField('hours', value);
      // 更新对应字段的错误,保留其他字段错误
      setClientErrorMessage(prev => ({
        ...prev,
        hours: error ? [error] : undefined
      }));
    }}
  />
  {clientErrorMessage?.hours && (
    <p className='py-2 text-xs text-red-500'>
      {clientErrorMessage.hours}
    </p>
  )}
</div>

修改Price字段:

<div className='mb-4'>
  <Label htmlFor='price' className='mb-2'>
    Price
  </Label>
  <div className='relative flex items-center max-w-2xl '>
    <Euro className='absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 transform' />
    <Input
      type='number'
      name='price'
      id='price'
      required
      className='pl-6'
      onBlur={(e) => {
        const value = Number(e.target.value);
        const error = validateSingleField('price', value);
        setClientErrorMessage(prev => ({
          ...prev,
          price: error ? [error] : undefined
        }));
      }}
    />
    {clientErrorMessage?.price && (
      <p className='py-2 text-xs text-red-500'>
        {clientErrorMessage.price}
      </p>
    )}
  </div>
</div>

步骤4:修正表单提交时的验证函数

原来的validateFields存在返回值错误,修正后确保返回合法数据或null:

const validateFields = (formData: FormData) => {
  const formValues = {
    client_id: formData.get('client_id'),
    hours: Number(formData.get('hours')),
    hours_left: Number(formData.get('hours')),
    price: Number(formData.get('price')),
    ends_at: formData.get('ends_at'),
  };

  const validationResult = createSchema.safeParse(formValues);

  if (!validationResult.success) {
    setClientErrorMessage(validationResult.error.flatten().fieldErrors);
    return null; // 验证失败返回null,终止后续操作
  }

  return validationResult.data; // 返回验证通过的合法数据
};

步骤5:更新表单Action逻辑

确保只有验证通过时才调用服务端Action:

<form
  ref={formRef}
  action={async (formData) => {
    const validatedData = validateFields(formData);
    if (!validatedData) return; // 验证失败直接返回

    const state = await createCard(validatedData);
    if (state.status === 'success') {
      formRef.current?.reset();
      setOpen(false);
      setClientErrorMessage({}); // 成功后清空所有错误
      setserverErrorMessage(undefined);
    } else if (state.status === 'error') {
      console.log(state);
      setserverErrorMessage(state.message);
    }
  }}
>
  {/* 其他表单内容不变 */}
</form>

关于是否需要单独创建Schema?

完全不需要。通过createSchema.shape[fieldName]可以直接获取对应字段的验证规则,既保证了字段验证规则和提交时的一致性,又避免了重复代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:35:02