Reputation: 6267
I would like to use react-query's useInfiniteQuery
. Everything seems clear but the pagination, part.
The documentation exemple is:
export default () => {
const {
status,
data,
error,
isFetching,
isFetchingMore,
fetchMore,
canFetchMore,
} = useInfiniteQuery(
'projects',
async (key, nextId = 0) => {
const { data } = await axios.get('/api/projects?cursor=' + nextId)
return data
},
{
getFetchMore: lastGroup => lastGroup.nextId,
}
)
I understand what it does. projects
is the id of the query, used for the cache, and then there is the function, that performs an api call. Now what about the nextId
variable? It starts at zero, so the backend will know it must only return results from 0 to x (can we also pass a dynamic limit by the way?). But when fetchMore()
- the function to trig the query again - is called, how on earth does useInfiniteQuery
knows it should increment nextId
?
Upvotes: 18
Views: 42611
Reputation: 641
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import axios from "axios";
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
interface PostQuery {
pageSize: number;
}
const usePosts = (query: PostQuery) =>
useInfiniteQuery<Post[], Error>({
queryKey: ["posts", query],
queryFn: ({ pageParam = 1 }) =>
axios
.get("https://jsonplaceholder.typicode.com/posts", {
params: {
_start: (pageParam - 1) * query.pageSize,
_limit: query.pageSize,
},
})
.then((res) => res.data),
staleTime: 1 * 60 * 1000, // 1m
keepPreviousData: true, // Keep data of current page instade of showing loading
getNextPageParam: (lastPage, allPages) => {
// 1->2
return lastPage.length > 0 ? allPages.length + 1 : undefined;
},
});
export default usePosts;
Upvotes: 0
Reputation: 13462
I'm trying to make an infinite scroll with rick and morty API and here's how I implemented it:
Your function (getCharacters
) should accept a key
and a prop (nextPage
) and returns an object that includes the data (data
) from your fetch and the page identifier (nextPage
) plus 1.
const getCharacters = async (key, nextPage = 1) => {
const res = await fetch(`${BASE_URL}/character/?page=${nextPage}`);
const { results } = await res.json();
return {
data: results,
nextPage: nextPage + 1,
};
};
const { status, data, fetchMore } = useInfiniteQuery(
['characters'],
getCharacters,
{
getFetchMore: (lastGroup, allGroups) => lastGroup.nextPage, // nextPage because we named it nextPage
}
);
const paginatedData = [];
data.forEach((page) => {
page.data.forEach((char) => {
paginatedData.push(char);
});
});
console.log(paginatedData) // The whole data
Update:
A better example using Rick and Morty API
async function getCharacters(page: number): Promise<Response> {
const response = await fetch('https://rickandmortyapi.com/api/character?page=' + page)
const characters = (await response.json())
return characters;
}
const { ... } = useInfiniteQuery(['characters'], ({ pageParam = 1 }) => getCharacters(pageParam), {
getNextPageParam: (lastPage) => {
const nextUrl = lastPage.info.next
if (nextUrl) {
// Return next page number
return Number(nextUrl.charAt(nextUrl.length - 1))
}
// Return false means no next page
return false
}
})
Upvotes: 9
Reputation: 3166
The steps are:
- Waiting for useInfiniteQuery to request the first group of data by default.
- Returning the information for the next query in getNextPageParam.
- Calling fetchNextPage function.
Reference https://react-query.tanstack.com/guides/infinite-queries
Example 1 with rest api
const fetchProjects = ({ pageParam = 0 }) =>
fetch('/api/projects?cursor=' + pageParam)
const {
data,
isLoading,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage) => {
// lastPage signature depends on your api respond, below is a pseudocode
if (lastPage.hasNextPage) {
return lastPage.nextCursor;
}
return undefined;
},
})
Example 2 with graphql query (pseudocode)
const {
data,
fetchNextPage,
isLoading,
} = useInfiniteQuery(
['GetProjectsKeyQuery'],
async ({ pageParam }) => {
return graphqlClient.request(GetProjectsQuery, {
isPublic: true, // some condition/variables if you have
first: NBR_OF_ELEMENTS_TO_FETCH, // 10 to start with
cursor: pageParam,
});
},
{
getNextPageParam: (lastPage) => {
// pseudocode, lastPage depends on your api respond
if (lastPage.projects.pageInfo.hasNextPage) {
return lastPage.projects.pageInfo.endCursor;
}
return undefined;
},
},
);
react-query will create data
which contains an array called pages
. Every time you call api with the new cursor/page/offset it will add new page to pages. You can flatMap data, e.g:
const projects = data.pages.flatMap((p) => p.projects.nodes)
Call fetchNextPage
somewhere in your code when you want to call api again for next batch, e.g:
const handleEndReached = () => {
fetchNextPage();
};
Graphql example query:
add to your query
after: cursor
:
query GetProjectsQuery($isPublic: Boolean, $first: Int, $cursor: Cursor) {
projects(
condition: {isPublic: $isPublic}
first: $first
after: $cursor
) ...
Upvotes: 7
Reputation: 21
The function getFetchMore()
has a lastGroup
parameter. With this param, you have to check if your response (lastGroup
parameter) has more data to fetch or not. In this case, I return a page
variable, which will be the next page to fetch when you call fetchMore()
and our fetchPlanets()
async function gets this page
variable as a parameter to concat()
the new api_url
.
I had a problem with the refetch on window focus because it had an issue to fetch the next group (page
). So, I pass refetchOnWindowFocus: false
in the options parameter.
const fetchPlanets = async (key, page = '1') => {
const api_url = `https://swapi.dev/api/planets/?page=${page}`;
console.log(api_url);
const res = await fetch(api_url);
return res.json();
};
const Planets = () => {
const { status, data, isFetching, isFetchingMore, fetchMore, canFetchMore } = useInfiniteQuery(
['planets'],
fetchPlanets,
{
refetchOnWindowFocus: false,
onSuccess: () => console.log('data fetch with no problemo!'),
onError: () => console.log('data fetch error!'),
getFetchMore: (lastGroup) => {
lastGroup.page = lastGroup.next?.split('=')[1];
return lastGroup.page;
},
},
);
console.log('data', data);
Upvotes: 2