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?
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 :)