DEV Community

Cover image for Building Infinite Scrolling in React Js with React Query v5
wassim93
wassim93

Posted on

Building Infinite Scrolling in React Js with React Query v5

Introduction:

In the world of web development, building a todo list application is a rite of passage. In this tutorial, we'll take it a step further by integrating TypeScript for type safety and React Query for efficient data management. By the end, you'll have a fully functional todo list application with robust error handling, real-time updates, and type-safe code.

Step 1: Setting Up Your Project

To get started, let's set up a new React project with TypeScript and React Query. We'll use Vite for fast development and a modern build setup.



npm init vite@latest infinite-scroll-app --template react-ts


Enter fullscreen mode Exit fullscreen mode

after that you have to select react as our option here
framework-reactand then we will select typescript + swc (Speedy Web Compiler ) you can discover more details about it through this link https://www.dhiwise.com/post/maximize-performance-how-swc-enhances-vite-and-react
typescriptAfter finishing this you have to change directory to the project created and install dependencies



# Change directory
cd infinite-scroll-app
# install dependencies
npm install
# Install React Query
npm install react-query@latest


Enter fullscreen mode Exit fullscreen mode

Step 2: Configuring ReactQuery within our project

In order to make react query works ensure that you've wrapped your application with QueryClientProvider and provided a QueryClient instance.So your main.tsx file will look like this



import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);


Enter fullscreen mode Exit fullscreen mode

To Get data we will be using Fake APi JSON Placeholder with the Todo endpoint.

Step 3: Fetching data

In this step we will be fetching our data from the API and then we set the limit to 10. The expected behavior for this is to render 10 values on the screen when the page is initially loaded and then continuously add more values when the user reaches the bottom screen.
So for this purpose and to make our project organized we are going to create a folder called services and add file called api.ts inside

project_architecture

Now let's create the function to fetch our data:



export const fetchData = async (page: number) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos?_pages=${page}&_limit=${10}`
  );
  const todos = await response.json();
  return todos;
};


Enter fullscreen mode Exit fullscreen mode

Now let's go to our App.tsx file and implement the logic of implementing React infinite scrolling
Here, we will use the useInfiniteQuery hook from the React query library. Consider the block of code below:



  const { data, error, fetchNextPage, hasNextPage, isFetching, isLoading } = useInfiniteQuery({
    queryKey: ["todos"],
    queryFn: ({ pageParam }) => fetchData(pageParam),
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) => {
      return lastPage.length > 0 ? allPages.length + 1 : undefined;
    },
  });


Enter fullscreen mode Exit fullscreen mode

The options for useInfiniteQuery are identical to the useQuery hook with the addition of the following:

  • initialPageParam : Required : The default page param to use when fetching the first page.

  • getNextPageParam : Required : When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages, as well as pageParam information.

The useInfiniteQuery hook return those properties:

  • data, which contains the data from the API.

  • error, which handles any error from fetching the data from the API.

  • fetchNextpage, which fetches the next set of data to be displayed when the user reaches the bottom.

  • hasNextpage, which checks to see if there's any more data to be displayed.

  • isFetching, which shows when the data is currently being fetched.

  • isLoading, which shows when the first set of data is being loaded from the API.

Step 4: displaying data

In this step we display the list of data that will be renered on our initial page

The data properties for useInfiniteQuery will contain 2 attributes pages and pageParams

  • data.pages: TData[] Array containing all pages.
  • data.pageParams: unknown[] Array containing all page params. so we are going to format our returned data array before displaying them


  const flatData = useMemo(() => data?.pages.flat() ?? [], [data]);

  if (isLoading) return <h1>Loading...</h1>;

  if (error) return <h1>Error on fetch data...</h1>;

  return (
    <div>
      {flatData?.map((item) => (
        <div key={item.id}>
          <p>{item.title}</p>
        </div>
      ))}

      {isFetching && <div>Fetching more data...</div>}
    </div>
  );



Enter fullscreen mode Exit fullscreen mode

Step 5: Implementing scrolling behavior

To make the infinte scroll works peerfectly we have first to detect if we have reached the ned of page to trigger a new request and get new data
You might have been wondering how the DOM knows when the user scrolls to the bottom of the page. Well, that's where the IntersectionObserver API comes in! The IntersectionObserver API provides a way to detect an element's position relative to the root element or viewport.



   const observer = useRef<IntersectionObserver>();
  const lastElementRef = useCallback(
    (node: HTMLDivElement) => {
      if (isLoading) return;

      if (observer.current) observer.current.disconnect();

      observer.current = new IntersectionObserver((entries) => {
        console.log(entries);
        if (entries[0].isIntersecting && hasNextPage && !isFetching) {
          fetchNextPage();
        }
      });

      if (node) observer.current.observe(node);
    },
    [fetchNextPage, hasNextPage, isFetching, isLoading]
  );



Enter fullscreen mode Exit fullscreen mode

This piece of code observes the user's motion on the screen. If the user reaches the bottom of the screen, the IntersectionObserver is alerted and serves the next set of data. Technically, it takes note of the point where the last element in the data intersects and then if hasNextPage is true, it fetches the new set of data to display.

Step 6: Final Result

Full code for this component



import { useInfiniteQuery } from "react-query";
import { fetchData } from "./services/api";
import { useCallback, useMemo, useRef } from "react";

function App() {
  const observer = useRef<IntersectionObserver>();

  const { data, error, fetchNextPage, hasNextPage, isFetching, isLoading } = useInfiniteQuery({
    queryKey: ["todos"],
    queryFn: ({ pageParam }) => fetchData(pageParam),
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) => {
      return lastPage.length > 0 ? allPages.length + 1 : undefined;
    },
  });
  const flatData = useMemo(() => data?.pages.flat() ?? [], [data]);

  const lastElementRef = useCallback(
    (node: HTMLDivElement) => {
      if (isLoading) return;

      if (observer.current) observer.current.disconnect();

      observer.current = new IntersectionObserver((entries) => {
        console.log(entries);
        if (entries[0].isIntersecting && hasNextPage && !isFetching) {
          fetchNextPage();
        }
      });

      if (node) observer.current.observe(node);
    },
    [fetchNextPage, hasNextPage, isFetching, isLoading]
  );
  if (isLoading) return <h1>Loading...</h1>;

  if (error) return <h1>Error on fetch data...</h1>;

  return (
    <div>
      {flatData?.map((item) => (
        <div key={item.id} ref={lastElementRef}>
          <p>{item.title}</p>
        </div>
      ))}

      {isFetching && <div>Fetching more data...</div>}
    </div>
  );
}

export default App;



Enter fullscreen mode Exit fullscreen mode

and this will be our result

Recording-2024-05-16-at-01-15-49 hosted at ImgBB β€” ImgBB

Image Recording-2024-05-16-at-01-15-49 hosted in ImgBB

favicon ibb.co

So that's it guys , I hope you liked of this article and i helped you to learn somthing new :D.

To access the full code for this project and explore further, you can find the repository on my: GitHub

Top comments (0)