React自定义Dropdown组件选中值不显示问题排查求助
自定义Dropdown选中值不显示问题排查与修复
问题描述
开发自定义Dropdown列表时,数据源已填充,但点击选项(如“Los Angeles”)后,选中值无法替换“Location”占位符。组件由离职同事编写,涉及以下文件:
drop-down.tsx(Dropdown组件实现)cities.ts(城市数据源)step-1.tsx(组件调用处)
错误分析
1. 类型不匹配导致选中值无法匹配
Radio组件中定义的Option接口要求id为string类型:
interface Option { id: string; label: string; }
但cities.ts中的城市id是number类型:
export const cities: Option[] = [ {id: 1, label: "New York"}, // id为number类型 // ...其他城市项 ];
组件内selectedOption的类型是string,执行options.find((option) => option.id === selectedOption)时,number与string的全等比较(===)返回false,导致找不到对应选项,始终显示占位符。
2. 焦点事件冲突导致选项点击失效
外层触发区的onBlur事件会在点击下拉选项时提前触发,将active设为false,下拉框直接隐藏,可能无法正确触发单选框的onChange事件,进一步影响选中值的设置。
修复方案
方案1:统一ID类型(核心修复)
两种可选方式:
- 修改数据源:将
cities.ts中的id改为字符串类型,与组件接口对齐:
// cities.ts interface Option {id: string, label: string} export const cities: Option[] = [ {id: "1", label: "New York"}, {id: "2", label: "San Francisco"}, {id: "3", label: "Los Angeles"}, {id: "4", label: "Las Vegas"}, {id: "5", label: "Chicago"}, {id: "6", label: "Miami"}, ];
- 修改组件类型:让组件兼容
string和number类型的id:
// drop-down.tsx interface Option { id: string | number; label: string; } const [selectedOption, setSelectedOption] = useState<string | number>("");
方案2:修复焦点事件冲突
修改handleBlur方法,延迟关闭下拉框,确保选项点击事件能正常触发:
const handleBlur = () => { // 延迟100ms关闭,给选项点击事件留触发时间 setTimeout(() => setActive(false), 100); };
完整修复后的drop-down.tsx示例
import React, { useState } from "react"; import Image from "next/image"; interface Option { id: string | number; label: string; } interface RadioProps { options: Option[]; placeholder?: string; width?: string; height?: string; } const Radio: React.FC<RadioProps> = ({ options, placeholder, height, width }) => { const [selectedOption, setSelectedOption] = useState<string | number>(""); const [active, setActive] = useState<boolean>(false); const handleFocus = () => { setActive(true); }; const handleBlur = () => { setTimeout(() => setActive(false), 100); }; const handleSelect = (optionId: string | number) => { setSelectedOption(optionId); setActive(false); }; return ( <div className="relative text-[#BBBBEA]"> <div className={`select relative flex ${height} ${width} cursor-pointer flex-row items-center gap-x-[10px] border-[1px] border-[#9F76FF33] py-[15px] pl-[15px] pr-[10px] ${active ? "border-[#9F76FF]" : "border-[#9F76FF33]" } rounded-md text-[14px] leading-6 text-[#BBBBEA]`} onFocus={handleFocus} onBlur={handleBlur} tabIndex={0} > <span> {selectedOption ? options.find((option) => option.id === selectedOption)?.label : placeholder} </span> <Image className={ "ml-auto h-[24px] w-[24px] transition-transform duration-300" } src="..." alt="Arrow" style={{ transform: active ? "rotate(-180deg)" : "rotate(0deg)", }} width={24} height={24} unoptimized={true} /> </div> <div className={`options top-15 absolute left-0 w-full z-30 mt-2 rounded-md bg-[#1D1D37] transition-opacity duration-300 ${active ? "opacity-100" : "pointer-events-none opacity-0"}`} style={{ maxHeight: "500px", overflowY: "auto" }} > <div className="p-2"> <div className="flex flex-col"> {options.map((option) => ( <div key={option.id} className="py-1"> <input id={String(option.id)} name="option" type="radio" className="hidden" checked={selectedOption === option.id} onChange={() => handleSelect(option.id)} onFocus={handleFocus} /> <label htmlFor={String(option.id)} className={`block cursor-pointer rounded-md p-2 hover:bg-gray-700 ${selectedOption === option.id ? "bg-gray-700" : "" }`} > {option.label} </label> </div> ))} </div> </div> </div> </div> ); }; export default Radio;
内容的提问来源于stack exchange,提问作者nikita
相关产品推荐
相关产品推荐

