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

使用useInfiniteQuery时,如何在搜索查询变更后获取新数据的同时保留旧数据?

使用useInfiniteQuery时,如何在搜索查询变更后获取新数据的同时保留旧数据?

这个问题确实戳中了useInfiniteQuery的一个小痛点——它不像useQuery那样内置keepPreviousData选项,切换查询键时会直接清空旧数据,导致加载过程中页面空白。不过我们可以借助React Query的缓存机制,手动维护展示数据来解决这个需求,下面是具体的实现方案:

核心思路

当搜索关键词变化时,新的分页查询会启动,此时我们不立刻清空页面内容:

  1. 优先展示当前查询的最新数据(如果已加载完成)
  2. 如果新数据正在加载,就从React Query的缓存中取出上一次搜索的分页数据继续展示
  3. 等新数据加载完成后,自动切换到新的搜索结果

修改后的完整代码

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>
  );
};

关键细节说明

  1. previousSearchQuery的作用:用useRef保存上一次的搜索关键词,这样当搜索词变化时,我们能精准定位到旧查询的缓存键。
  2. 利用queryClient.getQueryData取缓存:React Query会自动缓存所有已完成的查询结果,借助这个方法我们可以直接获取旧搜索对应的分页数据。
  3. 展示逻辑的优先级:确保新数据加载完成后立刻切换,加载过程中稳定显示旧数据,避免页面空白。
  4. 兼容原有分页功能:fetchNextPage、hasNextPage等原有分页逻辑完全不受影响,只修改了展示数据的来源。

额外注意事项

  • 确保旧缓存存在:因为你设置了staleTime: 5分钟,旧查询的缓存会保留5分钟,足够覆盖绝大多数API的响应时间。如果你的API响应特别慢,可以适当延长cacheTime(默认也是5分钟)。
  • 加载状态的控制:我们修改了加载动画的显示条件,只有首次加载且无任何缓存数据时才显示,避免切换搜索词时重复弹出加载动画影响体验。

备注:内容来源于stack exchange,提问作者Eimantas Petrikas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 12:09:34