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

如何使用React Query更新Headless UI Listbox的激活项?

解决Headless UI Listbox激活项无法切换的问题

核心问题分析

  • 初始selected为undefined,导致{isSuccess && selected && ...}条件下Listbox完全不渲染,用户无法触发选择操作
  • Listbox.Option内部嵌套Link,干扰Headless UI的默认点击事件,导致onChange不触发
  • 用对象作为selected值,API重新获取数据后新对象与旧selected引用不同,导致选中状态失效

具体修复步骤

1. 初始化选中状态并调整渲染条件

修改selected初始值,确保Listbox能正常渲染,同时在API请求成功后匹配当前路由或默认选中第一项:

const [selected, setSelected] = useState(null);
const { pathname } = useLocation();

const { data, isLoading, isError, error, isSuccess } = useQuery(
  ["tracks"],
  getTracks,
  {
    onSuccess: (fetchedData) => {
      // 优先匹配当前路由对应的项
      const matchedItem = fetchedData.find(item => pathname === `/${item.id}`);
      // 无匹配项则默认选中第一个
      setSelected(matchedItem || fetchedData[0]);
    }
  }
);

// 修改渲染条件,只要API请求成功就渲染Listbox
{isSuccess && (
  <Listbox value={selected} onChange={setSelected}>
    {/* 原有Button代码 */}
  </Listbox>
)}

2. 处理Link与Option的事件冲突

去掉Listbox.Option内部的Link,改为在onChange回调中同时更新选中状态和页面导航,避免事件冲突:

<Listbox value={selected} onChange={(item) => {
  setSelected(item);
  navigate(`/${item.id}`);
}}>
  <Listbox.Button className="mb-10 flex max-w-[278px] justify-between rounded-31px bg-white py-5 px-[50px] font-inter text-2xl font-bold text-primary">
    <img src={ArrowImage} />
    {!isLoading && <span>{selected?.[i18n.language === "en" ? 'name_en' : 'name_ar']}</span>}
  </Listbox.Button>
  <Listbox.Options>
    {data?.map((item) => (
      <Listbox.Option
        key={item.id}
        value={item}
        className={({ active }) => 
          `hover:text-accent ${active ? 'bg-gray-100' : ''} ${selected?.id === item.id ? 'text-accent font-bold' : ''}`
        }
      >
        {i18n.language === "en" ? item.name_en : item.name_ar}
      </Listbox.Option>
    ))}
  </Listbox.Options>
</Listbox>

3. 用id存储选中状态(可选,增强可靠性)

如果API频繁重新获取数据,建议用item.id作为选中状态的存储值,避免引用类型比较的问题:

const [selectedId, setSelectedId] = useState(null);

const { data, isLoading, isSuccess } = useQuery(
  ["tracks"],
  getTracks,
  {
    onSuccess: (fetchedData) => {
      const matchedItem = fetchedData.find(item => pathname === `/${item.id}`);
      setSelectedId(matchedItem?.id || fetchedData[0]?.id);
    }
  }
);

<Listbox value={selectedId} onChange={(itemId) => {
  setSelectedId(itemId);
  navigate(`/${itemId}`);
}}>
  <Listbox.Button>
    {!isLoading && (
      <span>{
        data?.find(item => item.id === selectedId)?.[i18n.language === "en" ? 'name_en' : 'name_ar']
      }</span>
    )}
  </Listbox.Button>
  <Listbox.Options>
    {data?.map((item) => (
      <Listbox.Option
        key={item.id}
        value={item.id}
        className={({ active }) => 
          `hover:text-accent ${active ? 'bg-gray-100' : ''} ${selectedId === item.id ? 'text-accent font-bold' : ''}`
        }
      >
        {i18n.language === "en" ? item.name_en : item.name_ar}
      </Listbox.Option>
    ))}
  </Listbox.Options>
</Listbox>

额外注意点

  • 给Listbox.Options添加合适的样式(比如max-h-60 overflow-y-auto),避免选项被遮挡无法点击
  • 确认getTracks返回的每个item都包含唯一的id字段

内容的提问来源于stack exchange,提问作者Mohamed Nabil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 03:45:02