使用useInfiniteQuery时,如何在搜索查询变更后获取新数据的同时保留旧数据?
使用useInfiniteQuery时,如何在搜索查询变更后获取新数据的同时保留旧数据?
这个问题确实戳中了useInfiniteQuery的一个小痛点——它不像useQuery那样内置keepPreviousData选项,切换查询键时会直接清空旧数据,导致加载过程中页面空白。不过我们可以借助React Query的缓存机制,手动维护展示数据来解决这个需求,下面是具体的实现方案:
核心思路
当搜索关键词变化时,新的分页查询会启动,此时我们不立刻清空页面内容:
- 优先展示当前查询的最新数据(如果已加载完成)
- 如果新数据正在加载,就从React Query的缓存中取出上一次搜索的分页数据继续展示
- 等新数据加载完成后,自动切换到新的搜索结果
修改后的完整代码
import { useInfiniteQuery, useQueryClient, QueryFunctionContext, QueryKey } from '@tanstack/react-query'; import { useState, useMemo, useRef, useEffect } from 'react'; import axiosInstance from './your-axios-instance'; // 替换为你的axios实例 const fetchPaginatedIngredients = async ({ pageParam = 1, queryKey, }: QueryFunctionContext<QueryKey>) => { const [, searchQuery] = queryKey; const response = await axiosInstance.get("/api/ingredients", { params: { query: searchQuery, page: pageParam, limit: 20 }, }); return response.data; }; const SupplierIngredients = () => { const [searchQuery, setSearchQuery] = useState(""); const queryClient = useQueryClient(); // 用ref保存上一次的搜索关键词,用来定位旧查询的缓存 const previousSearchQuery = useRef(searchQuery); // 同步上一次的搜索关键词 useEffect(() => { previousSearchQuery.current = searchQuery; }, [searchQuery]); const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, } = useInfiniteQuery({ queryKey: ["ingredients", searchQuery], queryFn: fetchPaginatedIngredients, getNextPageParam: (lastPage) => { if (lastPage.currentPage < lastPage.totalPages) { return lastPage.currentPage + 1; } return undefined; }, staleTime: 1000 * 60 * 5, retry: 3, }); // 处理展示用的食材数据:优先用新数据,加载中用旧缓存 const ingredients = useMemo(() => { // 1. 新数据加载完成,直接使用 if (data) { return data.pages.flatMap((page) => page.data); } // 2. 新数据加载中,尝试从缓存取上一次的查询结果 if (isLoading) { const prevQueryKey = ["ingredients", previousSearchQuery.current]; const prevData = queryClient.getQueryData<typeof data>(prevQueryKey); if (prevData) { return prevData.pages.flatMap((page) => page.data); } } // 3. 首次加载或无缓存时返回空数组 return []; }, [data, isLoading, queryClient, previousSearchQuery]); // 只有首次加载且无任何缓存数据时才显示加载动画 const showLoadingSpinner = isLoading && ingredients.length === 0; return ( <div> <input value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search ingredients" /> {showLoadingSpinner && <div>Loading...</div>} <ul> {ingredients.map((ingredient) => ( <li key={ingredient.id}>{ingredient.name}</li> ))} </ul> {hasNextPage && ( <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage} > {isFetchingNextPage ? "Loading more..." : "Load More"} </button> )} </div> ); };
关键细节说明
previousSearchQuery的作用:用useRef保存上一次的搜索关键词,这样当搜索词变化时,我们能精准定位到旧查询的缓存键。- 利用
queryClient.getQueryData取缓存:React Query会自动缓存所有已完成的查询结果,借助这个方法我们可以直接获取旧搜索对应的分页数据。 - 展示逻辑的优先级:确保新数据加载完成后立刻切换,加载过程中稳定显示旧数据,避免页面空白。
- 兼容原有分页功能:
fetchNextPage、hasNextPage等原有分页逻辑完全不受影响,只修改了展示数据的来源。
额外注意事项
- 确保旧缓存存在:因为你设置了
staleTime: 5分钟,旧查询的缓存会保留5分钟,足够覆盖绝大多数API的响应时间。如果你的API响应特别慢,可以适当延长cacheTime(默认也是5分钟)。 - 加载状态的控制:我们修改了加载动画的显示条件,只有首次加载且无任何缓存数据时才显示,避免切换搜索词时重复弹出加载动画影响体验。
备注:内容来源于stack exchange,提问作者Eimantas Petrikas
相关产品推荐
相关产品推荐

