How to Implement Infinite Queries in React Query
dravidjones28
Posted on July 19, 2023
We're goona have a button for loading more data.
Now, in a real-world application, we often want to load more data once the user scrolls down and reaches the bottom of the page.
We'll explore that later and focus only on loading more data.
import { useInfiniteQuery } from "@tanstack/react-query";
import axios from "axios";
interface Users {
id: number;
name: string;
}
interface UserQuery {
pageSize: number;
}
const useUsers = (query: UserQuery) =>
useInfiniteQuery<Users[], Error>({
queryKey: ["users", query],
queryFn: () =>
axios
.get("https://jsonplaceholder.typicode.com/users", {
params: {
_page: 2,
_limit: query.pageSize,
},
})
.then((res) => res.data),
});
export default useUsers;
How to calculate page number?
(useInfiniteQuery) has a function called (getNextPageParam) and it has two parameters.
1.) lastPage
2.) allPages
1.) (lastPage) is the array of Users.
2.) (allPages) is the two dimensional array of Users
Each element in this array is the Array of users.
This parameter (allPages) contains the data for all Pages.
So (getNextPageParam) function should return the next page number.
for example- if you are in page one we should return page 2.
So, how do you calculate this?
(allPages) parameters contains data for all Pages.
so you can return allPages.length + 1 as a next page number.
const useUsers = (query: UserQuery) =>
useInfiniteQuery<Users[], Error>({
queryKey: ["users", query],
queryFn: ({ pageParam = 1 }) =>
axios
.get("https://jsonplaceholder.typicode.com/users", {
params: {
_page: pageParam,
_limit: 4,
},
})
.then((res) => res.data),
getNextPageParam(lastPage, allPages) {
return allPages.length + 1;
},
});
But at some point we are going reach the end the list. we don't want to increment the page number forever?
With JSON Placeholder, if we request data for a page that doesn't exist, we can get an empty array. At some point, lastPage is going to be an empty array.
We can give expression like this
getNextPageParam(lastPage, allPages) {
return lastPage.length > 0 ? allPages.length + 1 : undefined;
},
But this is not the way to implement this logic. It varies based on your backend because your API should return the total number of records in advance, allowing us to calculate the number of pages and determine when we will reach the last page.
When we click on the 'Load More' button, the React Query will call the (getNextPageParam) function to retrieve the next page number. It will then pass the page number to the query function (queryFn).
so (queryFn) we need to add a parameter and destructor it and call (pageParam)
import { useInfiniteQuery } from "@tanstack/react-query";
import axios from "axios";
interface Users {
id: number;
name: string;
}
interface UserQuery {
pageSize: number;
}
const useUsers = (query: UserQuery) =>
useInfiniteQuery<Users[], Error>({
queryKey: ["users", query],
// as a best practice we should initialize to 1,
// so we data for the
// first page
queryFn: ({ pageParam = 1 }) =>
axios
.get("https://jsonplaceholder.typicode.com/users", {
params: {
_page: pageParam,
_limit: 4,
},
})
.then((res) => res.data),
getNextPageParam(lastPage, allPages) {
return lastPage.length > 0 ? allPages.length + 1 : undefined;
},
});
export default useUsers;
So, in users component let's create a button, when clicking, it will fetch to next page.
import useUsers from "./hooks/useUsers";
const Users = () => {
const pageSize = 4;
const { data, error, isLoading } = useUsers({ pageSize });
if (isLoading) return <div>Loading.....!</div>;
if (error) return <div>{error.message}</div>;
return (
<div>
<button>Load More</button>
</div>
);
};
export default Users;
infiniteQuery has a function called fetchNextPage.
So when you click the button call fetchNextPage.
import useUsers from "./hooks/useUsers";
const Users = () => {
const pageSize = 4;
const { data, error, isLoading, fetchNextPage } = useUsers({ pageSize });
if (isLoading) return <div>Loading.....!</div>;
if (error) return <div>{error.message}</div>;
return (
<div>
<button onClick={() => fetchNextPage()}>Load More</button>
</div>
);
};
export default Users;
Now, we want to disable this button, while we are fetching next page.
infiniteQuery has a boolean property called isFetchingNextPage.
import useUsers from "./hooks/useUsers";
const Users = () => {
const pageSize = 4;
const { data, error, isLoading, fetchNextPage, isFetchingNextPage } =
useUsers({ pageSize });
if (isLoading) return <div>Loading.....!</div>;
if (error) return <div>{error.message}</div>;
return (
<div>
<button disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>
Load More
</button>
</div>
);
};
export default Users;
data object that we get from the infinite query is an instance of data.
In this object we have couple of properties
1.) pageParams
2.) pages -> contains data for (allPages).
import useUsers from "./hooks/useUsers";
const Users = () => {
const pageSize = 4;
const { data, error, isLoading, fetchNextPage, isFetchingNextPage } =
useUsers({ pageSize });
if (isLoading) return <div>Loading.....!</div>;
if (error) return <div>{error.message}</div>;
return (
<div>
{data.pages.map((page, index) => (
<div key={index}>
{page.map((user) => (
<li>{user.name}</li>
))}
</div>
))}
<button disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>
Load More
</button>
</div>
);
};
export default Users;
Thank you!
God bless!
Posted on July 19, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.