How to Implement Infinite Scrolling with React.js

·

4 min read

Introduction

Any application that has a huge amount of data to show on a screen negatively affects the overall performance of an application. To overcome this performance degradation there are some widely used techniques. In this post, we will learn about Infinite Scrolling.

Image credit:

Here we are not loading all the at once but instead in a chunk, as soon as we reach the bottom of the page we are loading more data and adding it to the previously loaded data unless there is no more data to load.

Before starting you can check out this to get an idea of what we are trying to build - infinite-scroll-react.netlify.app

Prerequisite

I'm assuming that you are familiar with some of the key concepts of React.js and data fetching -

  1. React Hooks - useState and useEffect hooks.
  2. Fetch data with Axios or fetch API ( we will use Axios in this blog ).
  3. Intersection Observer API, useRef and useCallback (we will thoroughly discuss these).

Implementation

So, we will use the IntersectionObserver API to implement an infinite scroll. We also need to use useRef Hooks to get the DOM node to observe. Let's do a little discussion about these.

IntersectionObserver

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport. Learn more about it here in mdn

The concept is that we will create a loading element with ref inside our list container. This element is our target. So we can observe intersecting through the target. If the target is intersecting, fetch new data.

useRef

useRef(initialValue) is a built-in React hook that accepts one argument as the initial value and returns a reference (aka ref). A ref is an object having a special property current. we are using it here because the value of the ref is persisted between component re-renderings so that it will not trigger unnecessary rerender.

In this guide, I will create a movie listing react app with infinite scroll using tmdb which allow us to fetch paginated data (movies). Step 1

  1. The first step will be to import the react hooks and Axios

    import axios from "axios";
    import { useCallback, useEffect, useRef, useState } from "react";
    
  2. Now we will create a ref and define some states for movies and the page numbers.

    const [movies, setMovies] = useState([]);
    const [pageNumber, setPageNumber] = useState(0);
    const loadingRef = useRef(null);
    
  3. Now we will define a function that will fetch the data from the API.

    const fetchMovies = useCallback(async () => {
     if (pageNumber > 0) {
       try {
         const { data } = await axios.get(
           `https://api.themoviedb.org/4/discover/movie?page=${pageNumber}`,
           {
             headers: {
               Authorization: `Bearer ${apiKey}`,
             },
           }
         );
         setMovies((movies) => [...movies, ...data.results]);
       } catch (error) {
         console.log(error);
       }
     }
    }, [pageNumber]);
    

    Here we are using another hook called useCallback that returns a memoized callback.

  4. Now we will set up an effect hook useEffect to call the fetchMovies when the pageNumber gets changed. We are doing this because we are going to increase the pageNumber whenever we intersect the loading element.

    // fetching data
    useEffect(() => {
     fetchMovies();
    }, [fetchMovies]);
    
  5. Now we are going to create an observer to observe whether our loading element is intersecting.

    // observing dom node
    useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          setPageNumber((page) => page + 1);
        }
      },
      { threshold: 0.5 }
    );
    if (loadingRef.current) observer.observe(loadingRef.current);
    
    return () => {
      if (observer.current) {
        observer.unobserve(observer.current);
      }
    };
    }, [loadingRef]);
    

    Here in the callback of IntersectionObserver, we get access to the entries and select the first entries since we are only observing one loading element. IntersectionObserver also takes an additional object with properties {threshold: 0.5}, this means it will intersect when the 50% of the element is visible on the screen. And last we are unobserved the listener in the cleanup function of useEffect. That's all for functionality now let's will continue with UI.

  6. Let's create the UI, I'm going with a simple UI you can find all the CSS in the git repo here

    //App.js
    <>
       <header>
         <h1>Movies</h1>
         <p>{movies.length} movies</p>
       </header>
       <div className="movies">
         {movies.map((movie) => {
           return <Movie movie={movie} key={movie.id} />;
         })}
       </div>
       <div className="loader" ref={loadingRef}>
         <h3>Loading....</h3>
       </div>
     </>
    
    //Movie.jsx
    export const Movie = ({movie}) => {
    return (
     <div className='movie'>
         <div className='poster'>
             <img src={`${process.env.REACT_APP_IMAGE_BASE_URL}${movie.poster_path}`} alt="movie poster"/>
         </div>
         <h2>{movie.original_title}</h2>
     </div>
    )
    }
    

    Conclusion

    Now you know how to implement infinitely scrolling with IntersectionObserver. Although this is a simple implementation we can do a lot of improvements like creating a custom hooks useIntersectionObserver() but this will definitely give you a good understanding of how infinite scrolling works.

    Github Link

    Live Link