如何用React Hook Form、Zod、ShadCN UI正确处理数字输入并规避控/非控状态问题
问题
我用ReactJS结合react-hook-form、Zod做表单验证,搭配ShadCN UI组件库开发表单。现在有一个需要验证为数字类型的数字输入字段,处理时遇到了受控/非受控状态的问题。
尝试过的方案及问题
- 默认值问题:不给数字输入设置defaultValue的话,React会抛出输入从非受控切换为受控的警告;设置
defaultValue={undefined}也会触发类似警告。 - 设置默认值为0:设为0能避免警告,但字段会预填充0,占位符没法显示,用户必须手动删除0才能输入值,体验不好。
- 用空字符串做默认值:把
""作为默认值不可行,因为输入期望数字类型,会导致类型错误。
需求
- 初始状态字段为空,占位符可见;
- 用Zod验证为数字类型;
- 避免受控/非受控状态相关警告;
- 输入框默认不显示0,保持为空直到用户输入值。
额外考虑
我希望把这个字段作为真正的数字类型处理,不想用把它视为字符串的变通方案,因为要充分利用Zod的数字相关方法(比如min、max、int)。
当前代码
// Zod Schema部分 age: z .string() .min(1, { message: 'Age is required' }) // "Возраст обязателен" .transform(value => (value === '' ? '' : Number(value))) .refine(value => !isNaN(Number(value)), { message: 'Please enter a valid number', // "Введите число" }), // 组件部分 function Home() { const form = useForm<Schema>({ mode: 'all', resolver: zodResolver(schema), defaultValues: { age: '', }, }); function onSubmit(data: z.infer<typeof schema>) { console.log('data', data); } return ( <div className='h-full p-4 flex items-center justify-center'> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className='w-[500px] border rounded-md p-6 space-y-3' > <h3>Form</h3> <FormField control={form.control} name='age' render={({ field }) => ( <FormItem className='w-full'> <FormLabel>Age</FormLabel> <FormControl> <Input type='number' placeholder='Age' {...field} /> </FormControl> <FormDescription>Age</FormDescription> <FormMessage /> </FormItem> )} /> <Button className='w-full'>Submit</Button> </form> <DevTool control={form.control} /> </Form> </div> ); } export default Home;
解决方案
核心思路是用undefined作为表单字段初始值,通过Zod的类型约束和react-hook-form的setValueAs处理输入值转换,同时让输入框在空值时显示为空字符串,完美解决所有问题。
1. 调整Zod Schema
直接定义数字类型的验证规则,兼容初始空状态:
import { z } from "zod"; const schema = z.object({ age: z .number({ invalid_type_error: "请输入有效的数字", required_error: "年龄为必填项", }) .int("年龄必须是整数") .min(1, "年龄不能小于1") .max(120, "年龄不能大于120") // 允许初始undefined状态,提交时强制验证非空 .optional() .refine(value => value !== undefined, "年龄为必填项"), });
这里用optional()放行初始空值,再通过refine确保提交时字段必须有值,同时直接使用Zod的数字专属验证方法。
2. 配置react-hook-form
设置默认值为undefined,并通过setValueAs自动处理输入值的类型转换:
const form = useForm<z.infer<typeof schema>>({ mode: "all", resolver: zodResolver(schema), defaultValues: { age: undefined, }, // 将输入的空字符串转为undefined,非空转为数字 setValueAs: (value) => { if (value === "") return undefined; return Number(value); }, });
3. 调整ShadCN Input渲染
手动处理输入框的value属性,避免受控/非受控警告:
<FormField control={form.control} name="age" render={({ field }) => ( <FormItem className="w-full"> <FormLabel>年龄</FormLabel> <FormControl> <Input type="number" placeholder="请输入年龄" // 关键:undefined时设为空字符串,消除警告 value={field.value ?? ""} onChange={field.onChange} onBlur={field.onBlur} /> </FormControl> <FormDescription>请输入您的年龄</FormDescription> <FormMessage /> </FormItem> )} />
不直接展开{...field},而是手动映射value,确保输入框在空状态下显示空字符串,同时保持表单数据的数字类型。
方案优势
- 初始状态输入框为空,占位符正常显示;
- 表单数据始终是数字类型,可直接用Zod的
min/max/int等方法; - 完全避免受控/非受控状态警告;
- 用户体验流畅,无需手动删除默认值。
内容的提问来源于stack exchange,提问作者GregUng
相关产品推荐
相关产品推荐

