DEV Community

dravidjones28
dravidjones28

Posted on

How to Implement Infinite Queries in React Query

Image description
We're goona have a button for loading more data.
Image description

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;

Enter fullscreen mode Exit fullscreen mode

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.
Image description

2.) (allPages) is the two dimensional array of Users
Image description

Image description

  • 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;
    },
  });
Enter fullscreen mode Exit fullscreen mode

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;
    },
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Image description
data object that we get from the infinite query is an instance of data.

Image description
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;

Enter fullscreen mode Exit fullscreen mode

Thank you!
God bless!

Top comments (0)