React Hook Form、Zod与datetime-local输入框配合失效求解决方案
问题
我定义了如下Zod表单Schema:
const FormSchema = z.object({ title: z.string().nonempty('A Title is required'), body: z.string().nonempty('A Message Body is required'), goTime: z.string().datetime() //also tried .date() })
我希望使用datetime-local类型的输入框来编辑goTime字段,但无论如何操作都无法让Schema验证与选择器正常协同工作。
我的useForm配置如下:
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>({ defaultValues: { title: title, //this is a string body: body || '', //this is a string? goTime: goTime?.toISOString().slice(0,16) //this is the datetime field. } })
输入框部分我尝试了多种配置:
<input type="datetime-local" // defaultValue={goTime?.toISOString().slice(0,16)} {...register('goTime', { setValueAs: (v) => new Date(v).toISOString().slice(0,16) })} /> {errors.goTime?.message && ( <p className='text-sm text-red-400'>{errors.goTime.message}</p> )}
所有尝试都无效,求一个完整可用的示例,要求从已有对象的时间戳设置输入框默认值,选择后通过验证并保存到数据库。
完整解决方案
核心问题是datetime-local输入框的格式与Zod默认验证规则不匹配,同时字段类型转换逻辑需要对齐。以下是完整可运行的示例:
1. 调整Zod Schema
datetime-local返回的格式是YYYY-MM-DDTHH:mm,而Zod的z.string().datetime()默认要求带时区的ISO格式(如2024-05-20T12:30:00.000Z)。我们可以自定义验证规则,同时添加转换逻辑直接生成适合数据库存储的标准ISO字符串:
import { z } from "zod"; // 推导表单字段类型 type Inputs = z.infer<typeof FormSchema>; const FormSchema = z.object({ title: z.string().nonempty('A Title is required'), body: z.string().nonempty('A Message Body is required'), goTime: z.string() // 验证datetime-local的格式合法性 .refine(val => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(val), 'Invalid datetime format') // 自动转换为标准ISO字符串,方便存入数据库 .transform(val => new Date(val).toISOString()) });
2. 正确配置useForm
默认值必须严格符合datetime-local支持的格式,若原始goTime是Date对象或时间戳,需做对应转换:
import { useForm } from "react-hook-form"; // 假设从后端获取的初始时间(可以是Date对象或时间戳) const initialGoTime = new Date(); // 示例:或传入1716183000000 const { register, handleSubmit, formState: { errors } } = useForm<Inputs>({ defaultValues: { title: "", // 替换为你的初始标题 body: "", // 替换为你的初始内容 // 将Date对象/时间戳转换为datetime-local支持的格式 goTime: initialGoTime ? new Date(initialGoTime).toISOString().slice(0, 16) : "" } });
3. 输入框组件实现
无需额外的setValueAs转换,直接注册输入框即可:
<form onSubmit={handleSubmit((data) => { // data.goTime已通过Zod转换为标准ISO字符串,可直接存入数据库 console.log(data); // 调用API保存逻辑,例如: // fetch('/api/save', { method: 'POST', body: JSON.stringify(data) }) })}> <div> <label>Title:</label> <input {...register('title')} /> {errors.title && <p className='text-sm text-red-400'>{errors.title.message}</p>} </div> <div> <label>Body:</label> <textarea {...register('body')} /> {errors.body && <p className='text-sm text-red-400'>{errors.body.message}</p>} </div> <div> <label>Go Time:</label> <input type="datetime-local" {...register('goTime')} /> {errors.goTime && <p className='text-sm text-red-400'>{errors.goTime.message}</p>} </div> <button type="submit">Submit</button> </form>
关键注意点
datetime-local的输入/输出格式固定为YYYY-MM-DDTHH:mm,必须和默认值、验证规则完全对齐- 数据库存储的转换逻辑放在Zod的
transform中完成,避免在输入框层做多余处理 - 不要在
register中添加setValueAs,否则会和Zod的转换逻辑冲突
内容的提问来源于stack exchange,提问作者Chris Valentine
相关产品推荐
相关产品推荐

