NextUI多选组件结合React Hook Form的TypeScript错误问题
解决NextUI多选组件结合React Hook Form的TypeScript类型不兼容问题
错误核心原因
错误源于React Hook Form的Controller绑定的groups字段类型是ProfileGroup[],但NextUI Select组件的value属性要求与选项匹配的string[]类型。代码中通过{...field}把Controller的field.value(ProfileGroup[])传递给了Select,直接触发类型不兼容报错。
解决方案
方案1:拆分Controller字段传递,避免value冲突
不需要把field.value传给Select,用selectedKeys控制选中状态,在onGroupChanged里手动更新表单的groups值,仅传递field.onBlur触发表单校验:
const ProfileDetails: React.FC<ProfileDetailProps> = ({ profileId }) => { const { register, handleSubmit, setValue, reset, control, formState: { errors } } = useForm<Profile>({ defaultValues: profile }) const [countries, setCountries] = useState<string[]>([]) const onGroupChanged = (groups: string[]) => { const profileGroups: ProfileGroup[] = [] groups.forEach((item: string) => { const [countryCode, groupName] = item.split(" ") profileGroups.push({ profile_id: profile?.id, country_code: countryCode, group: groupName }) }) setValue("groups", profileGroups) setCountries(groups) } useEffect(() => { if (profile) { reset(profile) // 从profile的groups转换为Select需要的string[]格式 const formattedKeys = profile.groups?.map(g => `${g.country_code} ${g.group}`) ?? [] setCountries(formattedKeys) } }, [profile, reset]) return ( <div> <form onSubmit={handleSubmit(onSubmit)}> <Controller name="groups" control={control} rules={{ required: "Country group is required" }} render={({ field: { onBlur } }) => ( <Select items={countryGroupOptions} label="Country" labelPlacement="outside" placeholder="Select" selectionMode="multiple" selectedKeys={countries} onChange={onGroupChanged} onBlur={onBlur} > {(item) => <SelectItem key={item.value}>{item.label}</SelectItem>} </Select> )} ></Controller> </form> </div> ) }
方案2:使用Controller类型转换能力(更内聚)
利用Controller的字段转换逻辑,让表单内部存储ProfileGroup[],自动转换为Select需要的string[]:
const ProfileDetails: React.FC<ProfileDetailProps> = ({ profileId }) => { const { register, handleSubmit, reset, control, formState: { errors } } = useForm<Profile>({ defaultValues: profile }) // 将选中的string[]转为ProfileGroup[] const toProfileGroups = (selectedKeys: string[]) => { return selectedKeys.map(item => { const [countryCode, groupName] = item.split(" ") return { profile_id: profile?.id, country_code: countryCode, group: groupName } }) } // 将ProfileGroup[]转为Select需要的string[] const toSelectKeys = (groups: ProfileGroup[]) => { return groups.map(g => `${g.country_code} ${g.group}`) } return ( <div> <form onSubmit={handleSubmit(onSubmit)}> <Controller name="groups" control={control} rules={{ required: "Country group is required" }} render={({ field: { value, onChange, onBlur } }) => ( <Select items={countryGroupOptions} label="Country" labelPlacement="outside" placeholder="Select" selectionMode="multiple" selectedKeys={toSelectKeys(value)} onChange={(keys) => onChange(toProfileGroups(keys as string[]))} onBlur={onBlur} > {(item) => <SelectItem key={item.value}>{item.label}</SelectItem>} </Select> )} ></Controller> </form> </div> ) }
关键说明
- 方案1通过本地状态维护Select选中状态,逻辑直观,适合简单场景。
- 方案2利用React Hook Form的转换能力,避免额外状态,逻辑更内聚。
- 两种方案均避免了将
ProfileGroup[]直接传递给Select的value属性,彻底解决TypeScript类型冲突。
内容的提问来源于stack exchange,提问作者claudios
相关产品推荐
相关产品推荐

