react-hook-form+react-select+yup 条件下拉与默认值异常问题
问题原因
问题出在react-hook-form的Controller使用逻辑、react-select分组选项的取值逻辑上,和两个库本身的兼容性无关,核心错误有3个:
- 分组选项结构为
{label: 分组名, options: 子选项数组},原有代码用options.find(c => c.value === field.value)仅查找第一层数组,分组模式下第一层是分组对象,不存在value字段,永远返回undefined,导致UI无法匹配选中值 - 传给Controller的
defaultValue传入了完整option对象,但表单中该字段存储的是value字符串(onChange逻辑里只传了selectedOption.value),类型不匹配导致初始值映射失败 - 条件渲染两种不同选项结构的SelectInput时未加唯一key,React会复用组件实例,切换模式时内部状态未重置
修复步骤
1. 改造SelectInput组件,兼容普通/分组选项
新增递归查找选项的方法,修正value映射逻辑,同时明确defaultValue接收字符串类型的值:
import * as React from "react"; import { Controller } from "react-hook-form"; import Select from "react-select"; // 递归查找选项,兼容平铺、分组两种结构 const findMatchedOption = (optionList, targetValue) => { for (const item of optionList) { if (item.options) { const childMatch = findMatchedOption(item.options, targetValue); if (childMatch) return childMatch; } if (item.value === targetValue) return item; } return null; }; type SelectInputProps = { control: any; options: any; name: string; errors: any; handleChange?: (value: any) => void; defaultValue: string | number; } & React.ComponentPropsWithoutRef<"div">; export default function SelectInput({ control, options, name, errors, handleChange, defaultValue }: SelectInputProps) { return ( <div> <Controller control={control} defaultValue={defaultValue} name={name} render={({ field }) => ( <Select {...field} id={name} instanceId={name} options={options} value={findMatchedOption(options, field.value)} onChange={(selectedOption) => { field.onChange(selectedOption.value); handleChange?.(selectedOption.value); }} /> )} /> <p>{errors[name]?.message}</p> </div> ); }
额外提示:原组件中定义的register参数没有实际作用,Controller已经自动完成字段注册,可直接删除该参数定义和对应传参。
2. 修正App.js中的组件传参与渲染逻辑
给条件渲染的SelectInput加唯一key,强制切换类型时重挂载组件,同时将传入的defaultValue改为字符串值:
// 省略已有的options、schema定义代码 export default function App() { const { handleSubmit, control, watch, setValue, formState: { errors } } = useForm({ defaultValues: schema.cast(), resolver: yupResolver(schema) }); const watchMachineType = watch("machineType"); return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <label>Machine Type</label> <SelectInput name="machineType" errors={errors} control={control} options={machineTypeOptions} defaultValue={schema.cast().machineType} handleChange={(value) => { if (value === "single") { setValue("machine", machineOptions[0].value, { shouldValidate: true }); } else if (value === "multiple") { setValue("machine", machineGroupOptions[0].options[0].value, { shouldValidate: true }); } }} /> <label>Machine</label> {watchMachineType === "single" ? ( <SelectInput key="single-machine-select" name="machine" errors={errors} control={control} defaultValue={machineOptions[0].value} options={machineOptions} /> ) : ( <SelectInput key="group-machine-select" name="machine" errors={errors} control={control} defaultValue={machineGroupOptions[0].options[0].value} options={machineGroupOptions} /> )} <button type="submit">Submit</button> </form> ); }
修复效果
- 页面初始加载时,分组模式下的默认值可正常显示在UI上
- 切换machineType时,machine下拉框的选项结构、选中值和表单内部状态完全同步
- 表单提交可拿到正确的字符串类型值,校验逻辑正常触发
注意:react-hook-form的Controller中,
defaultValue和field.value存的是表单的原始业务值(也就是你最终提交需要的值,这里是字符串),react-select组件需要的option对象映射,要在render函数的value属性中单独处理,不要把整个option对象存在表单状态里。
内容的提问来源于stack exchange,提问作者Daniel Mason
相关产品推荐
相关产品推荐

