DEV Community

Cover image for πŸš€ Build an Infinite Scroll App in React like a Pro
Bishal Thapaliya
Bishal Thapaliya

Posted on

πŸš€ Build an Infinite Scroll App in React like a Pro

Infinite scrolling is probably one of the most widely used features in today's web applications. Rather than relying on traditional pagination, it loads further content dynamically when users scroll. In this tutorial, you will learn how to implement a basic and simple Infinite Scroll component in React JS by fetching GitHub users from the GitHub API.🎯

πŸ“πŸ”° Perfect for beginners! This guide focuses on the core logic without overwhelming you with complex React patterns.

🎯What We Will Build

We will build a clean React application showing GitHub users as cards. When users scroll, additional users will be fetched automatically, providing an overall smooth browsing experience.

πŸ›  Tech Stack:
βš›οΈ useState to manage state

βš›οΈ useEffect to handle side effects like API calls and event listeners

🌐 fetch API to load data

πŸ“œ Step-by-Step Implementation

1️⃣ Project Setup and Initial Imports
Ensure your React project is set up.

πŸ“ Tools like Vite ⚑️ make the process super fast.

Make a components directory inside the src folder, and create 2 files in it:

  • InfiniteScroll.jsx
  • InfiniteScroll.css

Import Essentials:
Open InfiniteScroll.jsx file and start with the following imports.

import React, { useEffect, useState } from 'react'
import './InfiniteScroll.css'
Enter fullscreen mode Exit fullscreen mode

In the given code snippet above, we are importing useState and useEffect from React, and we are importing a CSS file to style the user cards later.


2️⃣ States Configuration
We shall manage three primary pieces of state:

πŸ‘₯ users – stores the fetched users.

πŸ“„ page – track the current page.

πŸ”„ isLoading – prevents multiple simultaneous API calls.

const [users, setUsers] = useState([]);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
Enter fullscreen mode Exit fullscreen mode



3️⃣ Fetch Users from GitHub API
We are creating an async function, which allows us to use the await keyword in it. Using async makes it easier to write asynchronous code, which improves readability.

  • The getUsersFromAPI() function makes an asynchronous request to the GitHub API to retrieve a list of users, using the current page number to paginate the results.

  • When the data is successfully received, it adds the new users to the existing list and sets the next page number for retrieval.

  • If there is any exception in the process, it catches the exception and logs it to the console for debugging.

  • The function always stops the loading indicator, whether it is successful or not, to update the user interface accordingly.

const getUsersFromAPI = async () => {
    try {
        setIsLoading(true);
        const response = await fetch(`https://api.github.com/users?since=${(page - 1) * 30}`);
        const data = await response.json();
        setUsers((prevUsers) => [...prevUsers, ...data]);
        setPage((prevPage) => prevPage + 1);
    } catch (error) {
        console.error(`Error while fetching users: ${error}`);
    } finally {
        setIsLoading(false);
    }
}

Enter fullscreen mode Exit fullscreen mode



4️⃣ Handle Scroll Events

We will create a function called handleScroll, which checks if the user is near the bottom of the page. If yes, it will invoke the getUsersFromAPI() function to fetch more users.
It also ensures that, as a request is in progress, no new request is sent (isLoading prevents this).

const handleScroll = () => {
    if (isLoading) return;
    if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 10) {
        getUsersFromAPI();
    }
}
Enter fullscreen mode Exit fullscreen mode



5️⃣ Setup Lifecycle with useEffect

The useEffect hook runs once when the component is mounted.

  • It makes a call to the getUsersFromAPI() method to load the first list of users.
  • Adds a scroll event listener.
  • Cleans the listener when the component is removed.
useEffect(() => {
    getUsersFromAPI();
    window.addEventListener('scroll', handleScroll);

    return () => {
        window.removeEventListener('scroll', handleScroll);
    }
}, []);
Enter fullscreen mode Exit fullscreen mode



6️⃣ Render Users in Card Format

We iterate over the users array using the map() function and display each user in a stylish card layout. Each card displays the user's avatar, username, and account type (i.e., User or Organization), and a link to their GitHub profile is displayed when the card is hovered over.

{users.map((user) => (
    <div className="card" key={user.id}>
        <div className="image-container">
            <img src={user.avatar_url} alt={user.login} />
        </div>
        <div className="content-container">
            <div className="contentBx">
                <h3>
                    {user.login}<br />
                    <span>{user.type}</span>
                </h3>
            </div>
            <div className="sci">
                <a href={user.html_url} target='blank' rel='noopener noreferrer'>πŸ”— View Profile</a>
            </div>
        </div>
    </div>
))}
Enter fullscreen mode Exit fullscreen mode



Complete code snippet

import React, { useEffect, useState } from 'react'
import './InfiniteScroll.css'


const InfiniteScroll = () => {
    const [users, setUsers] = useState([]);
    const [page, setPage] = useState(1);
    const [isLoading, setIsLoading] = useState(false);

    // function to fecth users from Github API
    const getUsersFromAPI = async () => {
        try {
            setIsLoading(true);
            const response = await fetch(`https://api.github.com/users?since=${(page - 1) * 30}`);
            const data = await response.json();
            setUsers((prevUsers) => [...prevUsers, ...data]);
            setPage((prevPage) => prevPage + 1);
        } catch (error) {
            console.error(`Error while fetching users: ${error}`);
        } finally {
            setIsLoading(false);
        }
    }

    // function to check scroll position and load more if needed
    const handleScroll = () => {
        if (isLoading) return;

        if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 10) {
            getUsersFromAPI();
        }
    }

    // Initial load and attach scroll listener
    useEffect(() => {
        getUsersFromAPI();
        window.addEventListener('scroll', handleScroll);

        return () => {
            window.removeEventListener('scroll', handleScroll);
        }
    }, []);

    return (
        <>
            <h1 className='page-title'>Github Users</h1>
            <section>
                <div className="container">
                    {users.map((user) => (
                        <div className="card" key={user.id}>
                            <div className="image-container">
                                <img src={user.avatar_url} alt={user.login} />
                            </div>

                            <div className="content-container">
                                <div className="content">
                                    <h3>
                                        {user.login}<br />
                                        <span>{user.type}</span>
                                    </h3>
                                </div>
                                <div className="social">
                                    <a href={user.html_url} target='blank' rel='noopener noreferrer'>View Profile</a>
                                </div>
                            </div>
                        </div>
                    ))}
                </div>
            </section>
        </>
    )
}
export default InfiniteScroll
Enter fullscreen mode Exit fullscreen mode



7️⃣ Styling with CSS πŸ’…πŸŽ¨

CSS is essential for creating engaging user interfaces and enhancing the look and feel of your components.

For styling the InfiniteScroll component, simply copy the provided CSS code and paste it into your InfiniteScroll.css file.

Feel free to customize and experiment with the styles to match your app's design preferences. πŸ–ŒοΈ

➑️ In this tutorial, I am using nested CSS, a modern and in-demand trend that improves code readability and maintains cleaner structures, especially when working on component-based designs.

However, if you prefer, you can always stick to the traditional flat CSS syntax, which works just as well. The choice depends on your project needs and personal preference. ✨

.page-title {
  text-align: center;
  margin: 20px;
}

.container {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
  padding: 20px;

  .card {
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 250px;
    height: 200px;
    overflow: hidden;
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
    border-radius: 15px;

    &:hover .content-container {
      bottom: 0;
      transition-delay: 0s;
      cursor: pointer;
    }

    &:hover .content-container .content h3 {
        opacity: 1;
        transform: translateY(0px);
    }

    &:hover .content-container .social {
        transform: translateY(0px);
        opacity: 1;
        border: 1px solid #867cc1;
        padding: 5px;
        border-radius: 10px;
    }

    .image-container {
        position: relative;
        height: 100%;
        width: 100%;

        img {
            height: 100%;
            width: 100%;
            object-fit: cover;
        }
    }


    .content-container {
      position: absolute;
      bottom: -135px;
      width: 100%;
      height: 125px;
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 10;
      flex-direction: column;
      backdrop-filter: blur(15px);
      box-shadow: 0 -10px 10px rgba(0, 0, 0, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.2);
      transition: bottom 0.5s;
      transition-delay: 0.3s;


      .content {
        h3 {
        color: #fff;
        text-transform: capitalize;
        letter-spacing: 2px;
        font-weight: bold;
        font-size: 18px;
        text-align: center;
        margin: 10px 0 5px;
        line-height: 1.1em;
        transition: 0.5s;
        opacity: 0;
        transform: translateY(-20px);
        transition-delay: 0.5s;
        }

        span {
            font-size: 12px;
            font-weight: 300;
            text-transform: initial;
        }
      }


      .social {
            position: relative;
            bottom: 0x;
            display: flex;
            transform: translateY(40px);
            opacity: 0;
            transition-delay: 0.3s;

            a {
                color: #d3d3d3;
                font-size: 15px;
                text-decoration: none;
            }
        }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode



πŸ’‘ Key Learnings

βœ… useState – To manage data, page number, and loading state
βœ… useEffect – To handle side-effects and lifecycle events like API calls and scroll listeners
βœ… fetch API – To make HTTP requests to the GitHub API
βœ… map() – To dynamically render user cards
βœ… Scroll Event – To detect when the user reaches to the bottom of the page



πŸš€ Wrapping Up

In this tutorial, we explored how to build a smooth infinite scroll feature in React JS, pulling real-time data from the GitHub API and presenting it in an elegant card layout. πŸƒβœ¨

The focus was on keeping the code clean and beginner-friendly, sticking to the core React hooks like useState and useEffect without diving into complex patterns.

By following along, you now have a solid understanding of how state management, side effects, and event listeners can work together to create engaging, dynamic user experiences. πŸ’‘

If you made it this far, I’d love to hear your feedback or if there's anything you think I could have explained better.

Let’s keep learning together! πŸš€

Happy Coding! πŸ‘¨β€πŸ’»πŸ‘©β€πŸ’»πŸ’–

Top comments (0)