RadixUI Select组件与react-hook-form集成失效问题求助
解决React Hook Form与Radix UI Select的取值问题
问题背景
尝试通过React Hook Form获取Radix UI Select组件的选中值,使用Controller组件或forwardRef都未达到预期效果,测试代码如下:
'use client'; import React from 'react'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { FormField } from '../ui/forms'; import { TextArea } from '../ui/forms/TextArea'; import DropdownCause from './DropdownCause'; import DropdownShop from './DropdownShops'; import Button from '../ui/Button'; import { DataPicker } from '../ui/forms/DataPicker'; import { useForm, Controller } from 'react-hook-form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@radix-ui/react-select'; import DropdownNewTicket from './DropdownNewTicket'; import { MdArrowDropDown } from 'react-icons/md' const fakeData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] export default function NewTicketForm() { const session = useCurrentUser(); let name = session?.name ? session.name : ' '; const { handleSubmit, register, control } = useForm({ mode: 'all', reValidateMode: 'onChange', //resolver: zodResolver() }); const createTicket = (data: any) => { console.log(data); }; return ( <div className='border-2 border-primary p-3 rounded justify-center self-center w-2/3 h-5/6 scrollbar-thumb-slate-700 scrollbar-thin scrollbar-thumb-rounded flex flex-col items-center overflow-y-scroll'> <form className='flex flex-col gap-6' onSubmit={handleSubmit(createTicket)}> <FormField fieldName='Nome' className='w-80 rounded border border-primary indent-1 text-lg' value={name} {...register('username')} /> <DataPicker fieldName='Data da venda' {...register('sale-date')} /> <Controller control={control} name="product" rules={{ required: true }} render={({ field }) => ( <Select onValueChange={field.onChange} {...field} defaultValue='312321'> <SelectTrigger className='w-80 h-fit rounded-t rounded-tl border border-primary text-lg flex' asChild> <div className='flex justify-center'> <SelectValue placeholder="Select a verified email to display" /> <div className=''> <MdArrowDropDown size={25} /> </div> </div> </SelectTrigger> <SelectContent className='w-80 bg-foreground border border-primary h-80 overflow-y-scroll scrollbar-thumb-primary scrollbar-thin scrollbar-thumb-rounded'> {fakeData.map((index) => { const isFinal = index == fakeData.length return ( <SelectItem key={index} value={`Item ${index}`} className={`border-b text-center border-gray-400 w-full ${isFinal ? 'border-none' : 'border-b'} p-1`}>Item {index}</SelectItem> ) })} </SelectContent> </Select> )} /> <DropdownCause /> <DropdownShop /> <TextArea fieldName='Defeito' /> <Button text='Enviar' type='submit' style={'w-full p-1'} /> </form> </div> ); }
问题分析
- 错误传递props:将
{...field}直接传给Radix UI Select,Radix的Select组件不兼容React Hook Form的field对象中的部分props(如value的传递逻辑),导致取值失效。 - 无效默认值:设置的
defaultValue='312321'不存在于下拉选项中,组件无法识别该默认值。 - 状态脱节:未将
field.value绑定到Select的value属性,导致组件选中状态与表单状态不同步。
修复后的代码
'use client'; import React from 'react'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { FormField } from '../ui/forms'; import { TextArea } from '../ui/forms/TextArea'; import DropdownCause from './DropdownCause'; import DropdownShop from './DropdownShops'; import Button from '../ui/Button'; import { DataPicker } from '../ui/forms/DataPicker'; import { useForm, Controller } from 'react-hook-form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@radix-ui/react-select'; import DropdownNewTicket from './DropdownNewTicket'; import { MdArrowDropDown } from 'react-icons/md' const fakeData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] export default function NewTicketForm() { const session = useCurrentUser(); let name = session?.name ? session.name : ' '; const { handleSubmit, register, control } = useForm({ mode: 'all', reValidateMode: 'onChange', // 统一在useForm中设置表单默认值 defaultValues: { product: 'Item 1' } //resolver: zodResolver() }); const createTicket = (data: any) => { console.log(data); }; return ( <div className='border-2 border-primary p-3 rounded justify-center self-center w-2/3 h-5/6 scrollbar-thumb-slate-700 scrollbar-thin scrollbar-thumb-rounded flex flex-col items-center overflow-y-scroll'> <form className='flex flex-col gap-6' onSubmit={handleSubmit(createTicket)}> <FormField fieldName='Nome' className='w-80 rounded border border-primary indent-1 text-lg' value={name} {...register('username')} /> <DataPicker fieldName='Data da venda' {...register('sale-date')} /> <Controller control={control} name="product" rules={{ required: true }} render={({ field }) => ( // 仅绑定必要属性,避免传递不兼容props <Select value={field.value} onValueChange={field.onChange} onBlur={field.onBlur} // 可选:绑定失焦事件触发表单校验 > <SelectTrigger className='w-80 h-fit rounded-t rounded-tl border border-primary text-lg flex' asChild> <div className='flex justify-center'> <SelectValue placeholder="Selecione um produto" /> <div className=''> <MdArrowDropDown size={25} /> </div> </div> </SelectTrigger> <SelectContent className='w-80 bg-foreground border border-primary h-80 overflow-y-scroll scrollbar-thumb-primary scrollbar-thin scrollbar-thumb-rounded'> {fakeData.map((index) => { const isFinal = index === fakeData.length return ( <SelectItem key={index} value={`Item ${index}`} className={`border-b text-center border-gray-400 w-full ${isFinal ? 'border-none' : 'border-b'} p-1`}> Item {index} </SelectItem> ) })} </SelectContent> </Select> )} /> {/* 自定义Dropdown组件同样需要用Controller包裹并传递必要属性 */} <Controller control={control} name="cause" render={({ field }) => <DropdownCause value={field.value} onChange={field.onChange} />} /> <Controller control={control} name="shop" render={({ field }) => <DropdownShop value={field.value} onChange={field.onChange} />} /> <TextArea fieldName='Defeito' {...register('defect')} /> <Button text='Enviar' type='submit' style={'w-full p-1'} /> </form> </div> ); }
关键要点
- 精准绑定属性:仅将
field.value、field.onChange(可选field.onBlur)传递给Radix Select,避免直接spread不兼容的props。 - 有效默认值:确保默认值存在于下拉选项中,建议在
useForm的defaultValues中统一配置,而非组件内部单独设置。 - 自定义组件处理:如果
DropdownCause、DropdownShop是基于Radix Select封装的自定义组件,需在组件内部接收并绑定value和onChange属性,再用Controller包裹接入表单。
内容的提问来源于stack exchange,提问作者Bruno Bispo
相关产品推荐
相关产品推荐

