Introduction

We see infinite scrolling in applications and web pages especially social media that
 want us just to scroll. While mindlessly scrolling is not good, building your own
 infinite scroll is awesome. As a developer, we should try to recreate components
that we see while surfing the web. It can challenge you to learn more and think
 out of the box when implementing some components.

Also, If you are looking to implement an infinite scroll in your application then you
 can follow the guide to create your own. You can add your own code to improve
 the behavior of the scroll.

In this article, we are going to build an infinite scroll component from scratch. It
 will cover the following topics:

  • Environment Setup
  • Building the Component
  • Adding CSS
  • Optimizing the Infinite Scroll

    Now, let’s get started.

Environment Setup

We are going to use CRA to create the basic React application. You can do that my
 running the following command:

 npx create-react-app infinite-scroll

If you wish to Vite or NextJS then you can also. Apart from minor changes other
things will remain the same.

Note: To run this command, you need to have NodeJS pre-installed. Also,
 remove some of the unnecessary boilerplate code from CRA.

We are going to need one dependency to fetch data from an API. After setting
 React, we can install Axios with the following command:

  npm install axios

Now, we are ready to create the component.

App Component

We are going to build an component that is going to fetch popular movie data
 from Tmdb API. It’s free you can get their API key from their website. Let’s build
first where they are fetching the data and then add infinite scrolling features.

Here is the code for the App Component:

App.js

  import "./App.css";
    import { useState, useEffect } from "react";
    import axios from "axios";
    import { MovieCard } from "./MovieCard";

    function App() {
      const [page, setPage] = useState(1); // for number of page in tmdb 
      const [data, setData] = useState([]); // storing the fetched data
      const [loading, setLoading] = useState(false); // for setting loading state

      // fetching and stroring the data in the state
      const fetchMovie = async () => {
        const URL = `https://api.themoviedb.org/3/movie/popular?language=en-US&page=${page}`;
        const data = await axios.get(URL, {
          headers: {
            Authorization:
              "Bearer API KEY",
            Accept: "application/json",
          },
        });
        setData((prevData) => [...prevData, ...data.data.results]); // we are
 going to add the new data to current data.
        setLoading(false);
      };

      // useEffecte for invoking the function at the start
      useEffect(() => {
        fetchMovie();
      }, [page]);

      return (
        <div className="App">
          <header className="App-header">
            Popular movies according to Tmdb
            <div className="movieCardContainer">
              {data.length > 1 &&
                data.map((item) => {
                  return (
                    <MovieCard
                      key={item.id}
                      title={item.original_title}
                      description={item.overview}
                      rating={item.vote_average}
                      imageURL={item.poster_path}
                    />
                  );
                })}
              {loading && <h1>Loading....</h1>}
            </div>
          </header>
        </div>
      );
    }

    export default App;

You can pretty much understand the code, where we are fetching the data and
 passing it into the MovieCard component as a prop.

Create a MovieCard.js component for displaying each movie's info.

MoveCard.js

 import React from "react";

    export const MovieCard = ({ title, description, imageURL, rating }) => {

      const imagePath = `https://image.tmdb.org/t/p/w500${imageURL}`; // poster image path URL 

      return (
        <div className="movieCard">
          <img src={imagePath} height={400} />
          <div className="movieInfo">
            <h3>{title}</h3>
            <p>{description}</p>
            <p>{rating.toFixed(1)}</p>
          </div>
        </div>
      );
    };

Here is the CSS of the application;

App.css

 .App {
      text-align: center;
    }

    .App-header {
      background-color: #282c34;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding-top: 1em;
      font-size: calc(10px + 2vmin);
      color: white;
    }

    .movieCardContainer{
      margin-top: 1em;
      display: flex;
      flex-direction: column;
      gap: 1em;
      width: 60%;
      max-width: 800px;
    }

    .movieCard{
      display: flex;
    }

    .movieInfo{
      margin-left: 1em;
      text-align: left;
    }

    p{
      font-size: 18px;
    }

Infinite Scroll

Now, let’s first, understand how we are going to build the infinite scroll. For this,
 we are going to look at the scroll bar position. When the scroll bar position is just
 above the end of the page, we are going to set the loading state to true.

We are going to have another 

that is going to increment the page state
by 1. Once the page number is updated, the initial useEffect that has the page as a
dependency will trigger. This will invoke the fetchMovie() function to fetch the
 data.

Adding EventListner to Scroll

First, we are going to add even listen to know when the scroll bar position is
 changed.

   window.addEventListener("scroll", handleScroll);

handleScroll

When the scroll happens, we are going to check whether the current position of scoll
 bar is just above the bottom of the web page(ie total vertical scrollable area).
 If yes then we are changing the state of loading to true.

const handleScroll = () => {
      if (document.body.scrollHeight - 300 < window.scrollY + window.innerHeight) {
        setLoading(true);
      }
    };
  • scrollHeight : It is the property that returns the total height of the content,
     including the portion that is not visible on the screen. So, it will be the total
    scrollable area.
  • scrollY: It is the property that returns the number of pixels that the document
     has been scrolled vertically from the top. So it will be the area that has been
     scrolled.
  • innerHeight: It is the property that return the height of the browser’s Windows
    content area. It will be the scrollbar width. It is added to scrollY so that fetch
     happens when we reached the content rather than when we passed the
     content. ## useEffect

After successfully changing state of loading, we can implement a useEffect
 to incrementing the page number. So that, the fetching of the movie data can
 happen.

 useEffect(() => {
      if (loading == true) {
        setPage((prevPage) => prevPage + 1);
      }
    }, [loading]);

    // other useEffect that we already implemented

    useEffect(() => {
      fetchMovie();
    }, [page]);

Optimizing the eventListner

Since scroll can trigger handleScroll multiple times while scrolling, it will cause
 unnecessary invocation of the function multiple times. We can add debounce to
 the function so that it can take some time before invoking the function.

  // debounce function
    function debounce(func, delay) {
      let timeoutId;
      return function (...args) {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
          func(...args);
        }, delay);
      };
    }

    // adding debounce to the eventListner
    window.addEventListener("scroll", debounce(handleScroll, 500));

Here is the complete code of the App.js:

import "./App.css";
    import { useState, useEffect } from "react";
    import axios from "axios";
    import { MovieCard } from "./MovieCard";

    function App() {
      const [page, setPage] = useState(1);
      const [data, setData] = useState([]);
      const [loading, setLoading] = useState(false);
      const fetchMovie = async () => {

        const URL = `https://api.themoviedb.org/3/movie/popular?language=en-US&page=${page}`;
        const data = await axios.get(URL, {
          headers: {
            Authorization:
              "Bearer API KEY",
            Accept: "application/json",
          },
        });
        setData((prevData) => [...prevData, ...data.data.results]);
        setLoading(false);
      };
      useEffect(() => {
        fetchMovie();
      }, [page]);

      const handleScroll = () => {
        if (
          document.body.scrollHeight - 300 <
          window.scrollY + window.innerHeight
        ) {
          setLoading(true);
        }
      };

      function debounce(func, delay) {
        let timeoutId;
        return function (...args) {
          if (timeoutId) {
            clearTimeout(timeoutId);
          }
          timeoutId = setTimeout(() => {
            func(...args);
          }, delay);
        };
      }

      window.addEventListener("scroll", debounce(handleScroll, 500));

      useEffect(() => {
        if (loading == true) {
          setPage((prevPage) => prevPage + 1);
        }
      }, [loading]);

      return (
        <div className="App">
          <header className="App-header">
            Popular movies according to Tmdb
            <div className="movieCardContainer">
              {data.length > 1 &&
                data.map((item) => {
                  return (
                    <MovieCard
                      key={item.id}
                      title={item.original_title}
                      description={item.overview}
                      rating={item.vote_average}
                      imageURL={item.poster_path}
                    />
                  );
                })}
              {loading && <h1>Loading....</h1>}
            </div>
          </header>
        </div>
      );
    }

    export default App;

Here is the GIF demonstrating the workings of the application.


GIFInfinite Scrolling


Conclusion

Building an infinite scroll component in React can be a highly rewarding
 experience. It not only enhances your understanding of how scrolling works but
also teaches you about state management, event listeners, and optimization
 techniques like debouncing. By following this guide, you now have a basic infinite
 scroll setup that you can customize and improve according to your needs.

Whether you're displaying movie data, blog posts, or any other content, this
 component serves as a strong foundation. Remember, the key is to ensure a
 smooth user experience by carefully managing when and how data is fetched as
 the user scrolls. Happy coding!