如何在整个模式之外验证 Zod 模式的一个字段

问题描述 投票:0回答:1

我有一个包含多个输入的表单。

一是“时间”,一是“价格”。

我正在使用 NextJS 和服务器端操作。 我现在所做的是当有人点击提交

  • 使用 Zod 客户端验证整个表单
  • 如果有效,则发送到服务器,再次验证并执行数据库突变。

时间必须在10到100之间。我正在处理的场景如下;

  • 用户填写5
  • 按提交
  • Zod 说不,并显示错误“小时应在 10 到 100 之间”

到目前为止,这有效。但随后用户填写 50 并转到下一个字段。我希望运行验证

onBlur
,以便错误消息在现在有效时消失。否则,当输入为 50 但错误消息显示小时数应在 10 到 100 之间时,会令人困惑。

如何做到这一点?我是否必须为每个字段创建一个单独的架构并将其称为 onBlur?

形式:

    <form
              ref={formRef}
              action={async (formData) => {
                const validatedFields = validateFields(formData)

                const state = await createCard(validatedFields)
                if (state.status === 'success') {
                  formRef.current?.reset()
                  setOpen(false)
                  setClientErrorMessage(undefined)
                } else if (state.status === 'error') {
                  console.log(state)
                  setserverErrorMessage(state.message)
                }
              }}
            >
              <div className='mb-4'>
                <Label htmlFor='client_id' className='mb-2'>
                  Client
                </Label>
                <Select name='client_id' required>
                  <SelectTrigger className='w-[240px]'>
                    <SelectValue placeholder='Select client' />
                  </SelectTrigger>
                  <SelectContent>
                    {clients?.map((client) => (
                      <SelectItem key={client.id} value={client.id}>
                        {client.name}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                {clientErrorMessage?.client_id && (
                  <p className='py-2 text-xs text-red-500'>
                    {clientErrorMessage.client_id}
                  </p>
                )}
              </div>
              <div className='mb-4'>
                <Label htmlFor='hours' className='mb-2'>
                  Hours
                </Label>
                <Input type='number' name='hours' id='hours' required />
                {clientErrorMessage?.hours && (
                  <p className='py-2 text-xs text-red-500'>
                    {clientErrorMessage.hours}
                  </p>
                )}
              </div>
              <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'
                  />
                  {clientErrorMessage?.price && (
                    <p className='py-2 text-xs text-red-500'>
                      {clientErrorMessage.price}
                    </p>
                  )}
                </div>
              </div>
              <div className='mb-4 flex flex-col'>
                <div className='flex items-center'>
                  <Switch
                    checked={customEndDate}
                    className='mr-2'
                    onCheckedChange={handleSwitchChange}
                  />
                  <span className=''>Set custom end date</span>
                  <TooltipProvider>
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <InfoCircledIcon className='ml-1' />
                      </TooltipTrigger>
                      <TooltipContent>
                        <p>By default cards are valid for one year</p>
                      </TooltipContent>
                    </Tooltip>
                  </TooltipProvider>
                </div>
                {customEndDate ? (
                  <>
                    <Label htmlFor='ends_at' className='my-2 mr-2'>
                      Valid until
                    </Label>
                    <input
                      aria-label='Date'
                      type='date'
                      id='ends_at'
                      name='ends_at'
                      required
                      defaultValue={formattedDate}
                    />
                  </>
                ) : (
                  <input
                    type='hidden'
                    aria-label='Date'
                    id='ends_at'
                    name='ends_at'
                    required
                    defaultValue={formattedDate}
                  />
                )}
              </div>
              <p aria-live='polite' className='sr-only'>
                {state?.message}
              </p>
              <FormError errorMessage={serverErrorMessage} />
              <DialogClose asChild>
                <Button variant='outline' className='mr-2'>
                  Cancel
                </Button>
              </DialogClose>
              <SubmitButton normal='Add card' going='Adding  card...' />
            </form>

验证字段:

    const validateFields = (formData: FormData) => {
    const validatedFields = createSchema.safeParse({
      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'),
    })

    if (!validatedFields.success) {
      return setClientErrorMessage(validatedFields.error.flatten().fieldErrors)
    }

    return validateFields
  }

很想知道如何解决这个问题。

javascript forms validation next.js zod
1个回答
0
投票

您可以像这样引用另一个模式中的模式:

const a = z.object({
  client_id: z.number(),
  hours: z.number(),
  hours_left: z.number(),
  price: z.number(),
  ends_at: z.number()
});

const b = z.object({
  data: a,
  another_field: z.string(),
  and_another: z.object({})
});

b
的最终形状将是:

{
  a: {
    client_id: number;
    hours: number;
    // etc...
  },
  another_field: string;
  and_another: {}
}

您还可以使用

.extend()
:

扩展现有模式
const a = z.object({
  client_id: z.number(),
  hours: z.number(),
  hours_left: z.number(),
  price: z.number(),
  ends_at: z.number()
});

const extended_a = a.extend({
  another_field: z.string()
});

extended_a
的最终形状将是:

{
  client_id: number;
  hours: number;
  // etc...
  another_field: string
}

现在您可以使用架构的子集验证选定的字段,然后使用完整的扩展架构再次验证。

© www.soinside.com 2019 - 2024. All rights reserved.