如何使用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
相关产品推荐
相关产品推荐

