tanstackreact-query

useInfiniteQuery with tanstack-query and JSON:API: can't load more than one page


I have a JSON:API backend and a React app that uses tanstack-query v5.

I'm trying to use useInfiniteQuery() for paging. Based on the infinite queries docs, I came up with this code:

function useMyInfiniteQuery() {
  return useInfiniteQuery({
    ['myInfiniteQuery'],
    queryFn: async () =>
      getQueryMyStuff(
        'https://example.com/jsonapi/private?&filter[x.id][value]=2dd6dbda-4e17-45ff-b766-8c1047825d9c&sort=-c'
       ),
    initialPageParam: '',
    getNextPageParam: (lastPage) => {
      console.log('lastPage', lastPage);
      if (lastPage) {
        const nextLink = lastPage.links.next?.href;
        console.log('returning nextLink', nextLink);
        return nextLink;
      }
      console.log('no nextLink');
      return undefined;
    },
  });
}

const getQueryMyStuff = async (
  query: string,
): Promise<ResponseProcessedMemoriesWithPager> => {
  const response = await fetchAuth(query);
  if (isResponseDataNonEmptyArray(response)) {
    const { data } = response;
    if (isDataExpectedObject(data)) {
      const returnData: MyObject[] = [];
      data.map((element) => returnData.push(new MyObject(element)));
      return {
        data: returnData,
        links: response.links,
      };
    }
  }
  return false;
};

Then I created a React component with a "Load more" button:

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useMemoryPrivateAdmin(localStoragePreferences, userObject);

  if (status === 'pending') {
    return <p>Loading...</p>;
  }

  if (status === 'error') {
    return <p>Error: {error.message}</p>;
  }

  return (
    <>
      {data.pages.map((group, i) => (
        // eslint-disable-next-line react/no-array-index-key
        <React.Fragment key={i}>
          {group &&
            group.data.map((item) => (
              <ListItem key={item.id} object={item} />
            ))}
        </React.Fragment>
      ))}
      <div>
        <IonButton
          onClick={() => fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
        >
          {/* eslint-disable-next-line no-nested-ternary */}
          {isFetchingNextPage
            ? 'Loading more...'
            : hasNextPage
              ? 'Load More'
              : 'Nothing more to load'}
        </IonButton>
      </div>
      {isFetching && !isFetchingNextPage ? <p>Fetching...</p> : null}
    </>
  );

The problem is that when I click the Load more button, the original data is loaded again. So if the first load is A-B-C-D-E, when I click Load more, instead of getting F-G-H-I-J, I get A-B-C-D-E again (this new set of data is appended to the original A-B-C-D-E).

Each time I click Load more, it appends A-B-C-D-E.

However, in the console, when I check the value for nextLink, it is correct:

https://example.com/jsonapi/private?filter%5Bx.id%5D%5Bvalue%5D=2dd6dbda-4e17-45ff-b766-8c1047825d9c&sort%5B0%5D%5Bdirection%5D=DESC&sort%5B0%5D%5Blangcode%5D&sort%5B0%5D%5Bpath%5D=c&include=r&page%5Boffset%5D=50&page%5Blimit%5D=50

If I click this link, I see F-G-H-I-J as expected.

So there is something wrong with the way I am fetching the next page, but I can't tell what. getNextPageParam() returns the correct next page, but the Load more button loads the first page endlessly. What am I doing wrong?


Solution

  • getNextPageParam should only return the page number (eg: 5) instead of the whole link. You should then be passing the page param to query you're making.

    You probably want this

        queryFn: async ({pageParam = 0}) =>
          {
            const pageSize = 50
            const offset = pageParam*pageSize
            return getQueryMyStuff(
              `https://example.com/jsonapi/private?&filter[x.id][value]=2dd6dbda-4e17-45ff-b766-8c1047825d9c&sort=-c&page%5Boffset%5D=${offset}&page%5Blimit%5D=${pageSize}`
             )
           }
    

    And in your getNextPageParam

        getNextPageParam: (lastPage, pages) => {
          if (lastPage.links.next?.href) {
            return pages.length;
          }
          console.log('no nextLink');
          return undefined;
        },
    

    As you might have guessed, there are many ways to slice this problem based on how your backend is set up. I think the key thing here is that your backend is returning the href as opposed to just the page number. Hope this helps :)