DEV Community

Cover image for Simplify Infinite Loading with React Query
Super
Super

Posted on

Simplify Infinite Loading with React Query

Introduction

Fetching data in React applications can be a challenge, especially when it comes to infinite loading. React provides no built-in way to handle infinite loading, and it can be difficult to manage the state of data as it's being fetched.

Infinite loading is a common problem in web applications that display large amounts of data, such as social media feeds or product listings. Users expect these types of applications to load quickly and smoothly, even as more data is loaded.

Fortunately, there's a powerful library that can help simplify infinite loading in React applications: React Query. React Query provides a simple and efficient way to manage data fetching and caching in React applications, making it easier to handle infinite loading and improve the overall performance of your application.

Set up

We need react-query , of course:

yarn add react-query
Enter fullscreen mode Exit fullscreen mode

Then we need to use the provider, do this at your root file:

import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();

<React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>

Enter fullscreen mode Exit fullscreen mode

The QueryClient is created with const queryClient = new QueryClient();. This creates a new instance of the QueryClient that can be used to fetch and cache data.

The QueryClientProvider is a component that allows the QueryClient instance to be used throughout the application. The client prop is used to pass the QueryClient instance to the QueryClientProvider.

Finally, the App component is wrapped in the QueryClientProvider, which makes the QueryClient instance available to all of the child components of App.

Observer

To determine when the browser should fetch new data, we require a certain logic. This involves triggering a callback whenever the user scrolls to a particular point on the page. Let's write a hook for this:

export default function useIntersectionObserver({
  root,
  target,
  onIntersect,
  threshold = 1.0,
  rootMargin = "0px",
  enabled = true,
}) {
  React.useEffect(() => {
    if (!enabled) {
      return;
    }

    const observer = new IntersectionObserver(
      (entries) =>
        entries.forEach((entry) => entry.isIntersecting && onIntersect()),
      {
        root: root && root.current,
        rootMargin,
        threshold,
      }
    );

    const el = target && target.current;

    if (!el) {
      return;
    }

    observer.observe(el);

    return () => {
      observer.unobserve(el);
    };
  }, [target.current, enabled]);
}
Enter fullscreen mode Exit fullscreen mode

This code defines a custom hook named useIntersectionObserver that's used to observe when a certain element intersects with another element, typically the viewport. The hook uses the IntersectionObserver API to detect when the element becomes visible and triggers a callback function when it does.

The hook accepts an object as its argument with properties such as root, target, onIntersect, threshold, rootMargin, and enabled. These properties are used to configure the IntersectionObserver.

The useEffect hook is used to create and destroy the IntersectionObserver when the component is mounted and unmounted respectively. It also watches for changes in the target.current and enabled properties, and only creates the observer if the enabled flag is set to true.

The observer is created using the IntersectionObserver constructor and is passed a callback function that is triggered whenever an intersection is detected. This function checks if the observed element (target.current) is intersecting with another element, such as the viewport. If it is, the onIntersect callback function is called.

The observer is then attached to the observed element using the observer.observe(el) method, and the observer is removed from the observed element when the component is unmounted using the observer.unobserve(el) method.

Implementation

const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery(
    "starwars",
    async ({ pageParam = "https://swapi.dev/api/people/" }) =>
      await fetch(pageParam).then((res) => res.json()),
    {
      getNextPageParam: (lastPage) => lastPage.next ?? false,
    }
  );

  const loadMoreButtonRef = React.useRef();

  useIntersectionObserver({
    target: loadMoreButtonRef,
    onIntersect: fetchNextPage,
    enabled: hasNextPage,
  });
Enter fullscreen mode Exit fullscreen mode

First I found an API in the internet to get Star Wars characters (lucky me ), okay let's breakdown the code

  1. useInfiniteQuery Hook: The useInfiniteQuery hook from React Query is used to fetch and manage the data for the "starwars" query. It takes three arguments:
    • A query key (in this case, "starwars") to uniquely identify the query in the cache.
    • An asynchronous function that fetches and returns the data. In this case, it fetches data from the SWAPI using the fetch function, with the pageParam as the URL. If pageParam is not provided, it defaults to "https://swapi.dev/api/people/".
    • An options object containing the getNextPageParam function which determines the next page URL based on the response of the last page. It checks the next property of the lastPage object and returns it if it exists, otherwise, it returns false.

The hook returns an object containing properties such as data, fetchNextPage, hasNextPage, and isFetchingNextPage.

  1. React useRef Hook: The React.useRef() hook is used to create a mutable ref object (loadMoreButtonRef) that can be passed as a ref attribute to a DOM element (e.g., a "Load More" button).

  2. useIntersectionObserver Custom Hook: This custom hook utilizes the Intersection Observer API to detect when the loadMoreButtonRef element comes into the viewport

Conclusion

In conclusion, infinite loading can be a challenging aspect to implement in React applications, but the React Query library simplifies the process by providing an efficient way to manage data fetching and caching. By utilizing custom hooks such as useIntersectionObserver, developers can easily observe when an element comes into the viewport and trigger additional data fetching as needed. This allows for smooth and performant infinite loading experiences in web applications, meeting user expectations and improving the overall experience. With the help of React Query and a solid understanding of hooks, handling infinite loading in React applications becomes a much more manageable task.

Don't worry if this article is too obscure for you, check these out:

Top comments (2)

Collapse
 
onlinemsr profile image
Raja MSR

Thank you for sharing this amazing tutorial on how to simplify infinite loading with React Query.

I think, from SEO perspective this is not the best practices.

Collapse
 
avwerosuoghene profile image
Avwerosuoghene Darhare-Igben

Thanks for shedding light on infinite loading.