react-hook-form将数字字段识别为字符串的问题求助
问题:Zod验证数字表单时出现「Expected number, received string」错误
我使用Zod进行表单类型验证,规则如下:
const formSchema = z.object({ numberField: z.number().min(1, "numberField is required"), });
配合react-hook-form的自定义组件使用,表单无法输入字符串,但提交时仍收到「Expected number, received string」的验证错误。相关组件代码如下:
表单组件代码
<form onSubmit={handleFormOnSubmit} className="space-y-4"> <div className="xs:col-span-full sm:col-span-3"> <NumberField name="numberField" label="Insert a number" control={control} /> </div> </form>
自定义NumberField组件代码
import React from "react"; import { useController } from "react-hook-form"; import type { FieldValues, UseControllerProps } from "react-hook-form"; import NumberInput from "../../Inputs/NumberInput"; import type { NumberInputProps } from "../../Inputs/NumberInput"; export type NumberFieldProps<P extends FieldValues> = UseControllerProps<P> & Omit<NumberInputProps, "name">; const NumberField = <P extends FieldValues>({ name, control, defaultValue, ...rest }: NumberFieldProps<P>) => { const { field: { onChange, onBlur, value, name: fieldName }, fieldState: { error }, } = useController({ name, control, defaultValue }); return ( <NumberInput name={fieldName} onChange={onChange} onBlur={onBlur} value={value as number} error={error?.message} {...rest} /> ); }; export default NumberField;
NumberInput组件代码
import React from "react"; import clsx from "clsx"; import type { InputHTMLAttributes } from "react"; export interface NumberInputProps extends InputHTMLAttributes<HTMLInputElement> { name: string; label: string; error?: string; helperText?: string; } const NumberInput: React.FC<NumberInputProps> = ({ label, error, helperText, className = "", name, required, value, ...inputProps }) => { const inputClassName = clsx( "bg-gray-800 block border-0 flex-1 focus:ring-2 focus:ring-indigo-600 focus:ring-inset placeholder:text-gray-400 py-1.5 ring-1 ring-inset rounded-md shadow-sm sm:leading-6 sm:text-sm text-white w-full", { "ring-red-500": error, "ring-gray-700": !error, }, className, ); return ( <div className="w-full"> <label htmlFor={name} className="block text-sm font-medium leading-6 text-white" > {label} {required && <span className="text-red-500">*</span>} </label> <div className="mt-2"> <input type="number" name={name} id={name} className={inputClassName} value={Number(value)} {...inputProps} /> </div> {error && <p className="mt-1 text-sm text-red-500">{error}</p>} {helperText && <p className="mt-1 text-sm text-gray-400">{helperText}</p>} </div> ); }; export default NumberInput;
问题根源
- HTML number输入的本质:
type="number"的输入框,触发onChange时传递的仍然是字符串类型的值,哪怕输入的是数字。 - 组件只处理了渲染层转换:NumberInput中仅把
value转为Number(value)用于渲染,但没有修改onChange传递给react-hook-form的值类型,导致表单存储的始终是字符串,触发Zod的数字类型校验失败。 - 默认值缺失:未给NumberField设置数字类型的
defaultValue时,react-hook-form会将字段初始值设为undefined,用户首次输入的字符串值直接存入表单,未做转换。
修复方案
1. 在NumberInput中处理onChange,转换值类型
修改NumberInput组件,拦截onChange事件,将输入的字符串转为数字后再传递给react-hook-form:
const NumberInput: React.FC<NumberInputProps> = ({ label, error, helperText, className = "", name, required, value, onChange, ...inputProps }) => { const inputClassName = clsx( "bg-gray-800 block border-0 flex-1 focus:ring-2 focus:ring-indigo-600 focus:ring-inset placeholder:text-gray-400 py-1.5 ring-1 ring-inset rounded-md shadow-sm sm:leading-6 sm:text-sm text-white w-full", { "ring-red-500": error, "ring-gray-700": !error, }, className, ); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { // 空值转为undefined,避免NaN const numericValue = e.target.value ? Number(e.target.value) : undefined; // 传递转换后的数字给react-hook-form的onChange onChange?.(numericValue); }; return ( <div className="w-full"> <label htmlFor={name} className="block text-sm font-medium leading-6 text-white" > {label} {required && <span className="text-red-500">*</span>} </label> <div className="mt-2"> <input type="number" name={name} id={name} className={inputClassName} // 避免渲染NaN,空值显示为空字符串 value={value !== undefined ? value : ''} onChange={handleInputChange} {...inputProps} /> </div> {error && <p className="mt-1 text-sm text-red-500">{error}</p>} {helperText && <p className="mt-1 text-sm text-gray-400">{helperText}</p>} </div> ); };
2. 给NumberField设置数字类型的默认值(推荐)
使用NumberField时,指定defaultValue为数字,确保初始值类型正确:
<NumberField name="numberField" label="Insert a number" control={control} defaultValue={0} // 或根据需求设为null(需同步调整Zod规则) />
如果允许字段为空,需更新Zod规则:
const formSchema = z.object({ numberField: z.number().nullable().min(1, "numberField is required"), });
3. 可选:在react-hook-form resolver中预处理值
若不想修改组件,可在Zod resolver中添加预处理逻辑,将字符串转为数字后再验证:
import { zodResolver } from "@hookform/resolvers/zod"; const form = useForm({ resolver: zodResolver(formSchema, { preprocess: (values) => ({ ...values, numberField: values.numberField ? Number(values.numberField) : undefined, }), }), });
内容的提问来源于stack exchange,提问作者peternovak
相关产品推荐
相关产品推荐

