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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 09:22:07