You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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])

问题原因

  1. 手动维护isOpen状态与Headless UI内部的展开/收起逻辑冲突,导致组件重新渲染时状态被重置。
  2. 同时使用make和selectedMake两个状态,造成状态不一致。
  3. 多选模式下,onChange返回的是当前所有选中值的数组,原代码错误地只取第一个元素处理。
  4. 复选框未与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 20:57:12