React状态更新后重置:Headless UI Listbox多选异常排查
Headless UI Listbox多选状态频繁重置问题解决
问题描述
使用Headless UI的Listbox组件实现应用筛选的多选下拉菜单时,更新selectedMake状态(初始值["test"])会频繁重置回初始值。连续选择前3个选项后,控制台输出如下:
['test'](页面初始渲染) ['test', 'Hino'] <-- 第一次选择 ['test'] ['test', 'Bollinger'] ['test'] ['test', 'Isuzu'] ['test']
原组件代码
<Listbox value={make} onChange={(value) => handleSelect(value)} multiple> <div className="relative"> <Listbox.Button className={`w-[150px] flex space-between items-center border border-gray-300 rounded-sm pl-2 pr-1 py-[2.5px] focus:ring-[1.2px] focus:ring-tsgreen focus:outline-none ${make ? "text-gray-600" : "text-gray-400"}`} onClick={() => setIsOpen(prev => !prev)} > {selectedMake.length > 0 ? ( `${selectedMake[0]} (${selectedMake.length})` ) : "Make"} <FaChevronDown size={10} className="ml-auto text-gray-600" /> </Listbox.Button> {isOpen && ( <Listbox.Options static className="absolute w-full border border-gray-300 bg-white text-gray-600"> {make && ( <button className="relative cursor-pointer pl-7 py-[2px] text-gray-900 hover:font-medium" onClick={() => setIsOpen(false)} > <span>Clear</span> <span className="absolute inset-y-0 pl-1 left-0 top-0 flex items-center text-black"> <RiCloseFill size={22} /> </span> </button> )} {makes.map((make, i) => ( <Listbox.Option key={i} className={({ active }) => `relative cursor-pointer pl-2 py-[2px] ${active ? 'bg-lime-200/70 text-lime-900' : 'text-gray-900'}` } value={make} > {({ selected }) => ( <> <label className={`flex items-center space-x-2 py-[2px] ${false ? "bg-[#1e90ff] text-white" : ""}`}> <input type="checkbox" className="w-3 h-3 bg-white text-lime-600 rounded-sm border focus:ring-0 focus:ring-offset-0 focus:outline-0" /> <span className="flex-grow text-[15px]">{make}</span> </label> </> )} </Listbox.Option> ))} </Listbox.Options> )} </div> </Listbox>
原handleSelect函数
const handleSelect = (inp: string[]) => { const input = inp[0]; setSelectedMake(prev => { if (prev.includes(input)) { return prev.filter(m => m !== input); } else { return [...prev, input]; } }); };
原状态定义
const [isOpen, setIsOpen] = useState(false) const [make, setMake] = useState<string[]>([]); const [selectedMake, setSelectedMake] = useState<string[]>(["test"]); const makes = ["Hino", "Bollinger", "Isuzu", "Heil", "Battle Motors"]
日志代码
useEffect(() => { console.log(selectedMake); }, [selectedMake])
问题原因
- 手动维护
isOpen状态与Headless UI内部的展开/收起逻辑冲突,导致组件重新渲染时状态被重置。 - 同时使用
make和selectedMake两个状态,造成状态不一致。 - 多选模式下,
onChange返回的是当前所有选中值的数组,原代码错误地只取第一个元素处理。 - 复选框未与
Listbox.Option的选中状态绑定,UI与实际状态脱节。
修复方案
1. 简化状态定义
删除冗余的isOpen和make状态,统一用selectedMake管理选中值:
const [selectedMake, setSelectedMake] = useState<string[]>(["test"]); const makes = ["Hino", "Bollinger", "Isuzu", "Heil", "Battle Motors"];
2. 修正handleSelect函数
直接使用onChange返回的选中值数组更新状态,无需手动处理添加/删除:
const handleSelect = (selectedValues: string[]) => { setSelectedMake(selectedValues); };
3. 修复Listbox组件代码
移除自定义展开逻辑,绑定复选框选中状态,统一状态管理:
<Listbox value={selectedMake} onChange={handleSelect} multiple> <div className="relative"> <Listbox.Button className={`w-[150px] flex space-between items-center border border-gray-300 rounded-sm pl-2 pr-1 py-[2.5px] focus:ring-[1.2px] focus:ring-tsgreen focus:outline-none ${selectedMake.length > 0 ? "text-gray-600" : "text-gray-400"}`} > {selectedMake.length > 0 ? ( `${selectedMake[0]} (${selectedMake.length})` ) : "Make"} <FaChevronDown size={10} className="ml-auto text-gray-600" /> </Listbox.Button> <Listbox.Options static className="absolute w-full border border-gray-300 bg-white text-gray-600"> {selectedMake.length > 0 && ( <button className="relative cursor-pointer pl-7 py-[2px] text-gray-900 hover:font-medium" onClick={() => setSelectedMake([])} > <span>Clear</span> <span className="absolute inset-y-0 pl-1 left-0 top-0 flex items-center text-black"> <RiCloseFill size={22} /> </span> </button> )} {makes.map((make, i) => ( <Listbox.Option key={i} className={({ active }) => `relative cursor-pointer pl-2 py-[2px] ${active ? 'bg-lime-200/70 text-lime-900' : 'text-gray-900'}` } value={make} > {({ selected }) => ( <> <label className={`flex items-center space-x-2 py-[2px] ${selected ? "bg-[#1e90ff] text-white" : ""}`}> <input type="checkbox" checked={selected} className="w-3 h-3 bg-white text-lime-600 rounded-sm border focus:ring-0 focus:ring-offset-0 focus:outline-0" onChange={(e) => e.preventDefault()} /> <span className="flex-grow text-[15px]">{make}</span> </label> </> )} </Listbox.Option> ))} </Listbox.Options> </div> </Listbox>
关键修复点
- 移除自定义
isOpen状态,由Listbox自动管理下拉菜单的展开/收起。 - 统一使用
selectedMake绑定Listbox的value,确保状态唯一且一致。 - 复选框的
checked属性绑定到Listbox.Option的selected参数,UI与实际选中状态同步。 - 给复选框添加
onChange阻止默认行为,让Listbox的逻辑处理选中状态。
内容的提问来源于stack exchange,提问作者Noah Dammann
相关产品推荐
相关产品推荐

