使用HeadlessUI Combobox时Yup无法获取选中值的问题
问题:HeadlessUI Combobox结合Yup验证时始终触发必填错误
使用HeadlessUI的Combobox组件时,选中的值能正常写入selectedReward状态变量,但Yup仍持续抛出reward字段的必填错误提示。
相关代码
表单类型定义
// form values for Yup type RewardCreateFormValues = { name: string price: number quantity: number reward: string } type Reward = { id: string name: string price: number quantity: number } const rewards: Reward[] = [ { id: "1", name: "Reward 1", price: 100, quantity: 100 }, { id: "2", name: "Reward 2", price: 200, quantity: 200 }, { id: "3", name: "Reward 3", price: 300, quantity: 300 } ]
Combobox状态与过滤逻辑
const [selectedReward, setSelectedReward] = useState(rewards[0]) const [query, setQuery] = useState("") // it filters the rewards according to the query let filteredReward = query === "" ? rewards : rewards.filter(reward => { return reward.name.toLowerCase().includes(query.toLowerCase()) }) const rewardOptions = useMemo(() => { return filteredReward.map((reward: Reward) => ({ name: reward.name, id: reward.id })) }, [filteredReward])
Yup验证规则与初始值
const validationSchema = Yup.object().shape({ name: Yup.string().required("Required"), price: Yup.number().required("Required"), quantity: Yup.number().required("Required"), reward: Yup.string().required("Required") }) const initialValues: RewardCreateFormValues = { name: "", price: 0, quantity: 0, reward: "" }
Combobox组件代码
<GridItem colSpan={3}> <FormControl> <Box> <FormLabel>Select a Reward</FormLabel> <Combobox as="div" value={selectedReward} onChange={setSelectedReward}> <Combobox.Button as="div"> <Combobox.Input className="w-full rounded-md border-0 bg-white py-2 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-[#E2E8F0] focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" name="reward" onChange={(event: any) => setQuery(event.target.value)} placeholder="Select a Reward" displayValue={(selectedReward: Reward) => selectedReward ? selectedReward.name : "" } /> </Combobox.Button> <Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> {rewardOptions.map(reward => ( <Combobox.Option key={reward.id} value={reward} className={({ active }) => classNames( "relative cursor-default select-none py-2 pl-3 pr-9", active ? "bg-indigo-600 text-white" : "text-gray-900" ) } > {({ active, selected }) => ( <> <Box display="flex"> <Text as="span" className={classNames( "truncate", selected ? "font-semibold" : "font-normal" )} > {reward.name} </Text> </Box> {selected && ( <Text as="span" className={classNames( "absolute inset-y-0 right-0 flex items-center pr-4", active ? "text-white" : "text-indigo-600" )} > <CheckIcon className="h-5 w-5" aria-hidden="true" /> </Text> )} </> )} </Combobox.Option> ))} <Combobox.Option value="" className={ "relative cursor-default select-none py-2 pl-3 pr-9 bg-indigo-100 hover:bg-indigo-600 hover:text-white text-gray-900" } onClick={onOpen} > <Box display="flex" flexDirection="row" gap={2} alignItems="center" > <PlusIcon className="h-5 w-5" aria-hidden="true" /> <button>Create a Reward</button> </Box> </Combobox.Option> </Combobox.Options> </Combobox> </Box> <ErrorMessage name="reward" component="p" className="mt-2 text-sm text-red-600" /> </FormControl> </GridItem> <GridItem colSpan={3}> <Box display="flex" justifyContent="flex-end"> <Button isLoading={isSubmitting} isDisabled={isSubmitting} type="submit" colorScheme="indigo" variant="solid" > Create Listing </Button> </Box> </GridItem>
解决方案
问题核心是表单库(如Formik)的表单状态与自定义的selectedReward状态未同步,同时存在类型不匹配问题,导致Yup验证的reward字段始终为空。
1. 同步表单字段与Combobox选中值
在Combobox的onChange事件中,不仅更新selectedReward,还要同步更新表单的reward字段(以Formik为例,使用setFieldValue):
<Combobox as="div" value={selectedReward} onChange={(selectedItem) => { setSelectedReward(selectedItem); // 同步到表单的reward字段,传入选中项的id setFieldValue('reward', selectedItem.id); }} >
2. 修正初始值不匹配
selectedReward初始值是rewards[0],但表单初始值的reward为空字符串,导致初始状态验证不通过。修改初始值为对应选中项的id:
const initialValues: RewardCreateFormValues = { name: "", price: 0, quantity: 0, reward: rewards[0].id // 改为初始选中reward的id }
3. 统一Combobox选项的类型
当前rewardOptions返回仅包含name和id的对象,而selectedReward是完整的Reward对象,类型不匹配会导致选中逻辑异常。调整rewardOptions直接返回完整的Reward对象:
const rewardOptions = useMemo(() => { return filteredReward; // 直接返回完整的Reward数组 }, [filteredReward])
同时修改Combobox.Option的value为完整对象:
{rewardOptions.map(reward => ( <Combobox.Option key={reward.id} value={reward} // 传入完整的Reward对象 className={({ active }) => classNames( "relative cursor-default select-none py-2 pl-3 pr-9", active ? "bg-indigo-600 text-white" : "text-gray-900" ) } > {/* 原有内容不变 */} </Combobox.Option> ))}
4. 处理"Create a Reward"选项
该选项的value为空字符串,选中后会导致reward字段为空,需确保点击该按钮时不更新表单字段,而是直接打开创建弹窗:
<Combobox.Option value={null} // 改为null,避免空字符串干扰验证 className={ "relative cursor-default select-none py-2 pl-3 pr-9 bg-indigo-100 hover:bg-indigo-600 hover:text-white text-gray-900" } onClick={(e) => { e.preventDefault(); // 阻止Combobox的选中逻辑 onOpen(); // 打开创建弹窗 }} > {/* 原有内容不变 */} </Combobox.Option>
完成以上修改后,表单的reward字段会与Combobox选中值同步,Yup验证就能正常工作。
内容的提问来源于stack exchange,提问作者onurhan
相关产品推荐
相关产品推荐

