react-querytanstackreact-query

How to type response from useInfiniteQuery QueryFn?


I have a hook that use useInfiniteQuery:

const useGetData = (page: number = 1) => {
  return useInfiniteQuery({
    queryKey: ["data"],
    queryFn: () => getAllData(page),
    getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined,
    getNextPageParam: (lastPage) => lastPage.nextId ?? undefined,
  });
};

Data fetching is:

export const getAllData = async (page: number) => {
  return (await axios.get(`data?pageSize=15&pageNumber=${page}`)).data;
}

I want to return axios response typed like this:

return (await axios.get<Data[]>(`data?pageSize=15&pageNumber=${page}`)).data;

But when I do this I am getting typescript error in hook:

getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined,
getNextPageParam: (lastPage) => lastPage.nextId ?? undefined,

That previousId and nextId doesn't exist on type Data[]. What should I do to fix this typescript error?


Solution

  • I think there's a bit of a misunderstanding of how Infinite Queries work:

    1. they are one cache entry, split up into multiple pages
    2. each page is a separate fetch, so the QueryFunction is called for each page
    3. React Query provides a pageParam value to the QueryFunction. You have to use it, because that's how you can differentiate between which page is currently fetching.
    4. to find out what the next page in the chain is, you have to implement getNextPageParam.

    So if you fetch the first page, and it returns an Array of Data, as denoted by the type Data[], that is what gets passed to getNextPageParam. Now you need to tell React Query what the next page is, depending on that information.

    In our examples, we use a different structure: The server returns an object containing:

    {
      items: [{ id: 520, ... }, { id: 521, ... }, { id: 522, ... }],
      nextId: 523
    }
    

    with that structure, it's straightforward to find out what the next page param should be: lastPage.nextId.

    If you have a different structure, you need to implement getNextPageParam differently.

    As it seems that you have a paginated approach rather than a cursor based approach, it's good to know that we also provide the list of all pages to the getNextPageParam function. With that, you can say that the param for the next page is the length of the current pages plus one:

    const useGetData = () => {
      return useInfiniteQuery({
        queryKey: ["data"],
        queryFn: ({ pageParam }) => getAllData(pageParam),
        initialPageParam: 1,
        getNextPageParam: (lastPage, allPages) => allPages.length + 1
      });
    };
    

    pageParam will be 1 on the first fetch because of initialPageParam.

    After that, if you fetch the next page, we pass allPages, which has a length of 1 then, to getNextPageParam. For that, we return 2, so the second page will receive pageParam: 2 and can then fetch the second page. This will continue for the 3rd page, where allPages will have a length of 2, so we compute 3 and so on.