The Ultimate Guide to Implementing Infinite Scrolling in React

GeoJung Im
9 min readApr 9, 2023

--

Intro

When it comes to displaying multiple posts in a React project, there are several options to choose from, but two popular choices are infinite scrolling and pagination. During my own project, I encountered this decision-making process and ultimately decided to implement infinite scrolling. In this post, I’ll share my reasoning for choosing this method and walk you through how I successfully integrated it into my project.

Then what is an infinite scroll?

What Is an Infinite Scroll?

Infinite scrolling is a popular technique used on social media platforms like Twitter to display large amounts of data in a seamless manner. With infinite scrolling, as the user scrolls down the page, additional data is automatically loaded, eliminating the need for pagination or clicking on a “load more” button. The next set of data is fetched and displayed when the user reaches the end of the current content. This technique provides a more user-friendly experience, as it reduces the number of clicks and page reloads required to view content. Additionally, it allows for a more engaging and continuous browsing experience, which can lead to increased user retention and satisfaction.

Implementation of Infinite Scroll in react

  1. Scroll Listener: This approach involves attaching a scroll listener to the window object, which detects when the user scrolls and triggers a function to load more content. However, this method can cause performance issues as even small scroll movements can trigger a large number of events, resulting in decreased performance. Nonetheless, in certain cases, such as when dealing with small amounts of data, a scroll listener can be a more efficient solution.
  2. Intersection Observer: This method utilises the Intersection Observer API, which allows developers to track the intersection of an element with the viewport or other elements. By setting a threshold for when an element enters or exits the viewport, the observer can trigger a function to load more content. This approach is generally more efficient as it reduces the number of events fired and provides better performance, especially in mobile environments where scrolling is frequent.

However, it’s worth noting that Intersection Observer isn’t always the most efficient option and that the optimal choice may vary depending on the specific use case. Ultimately, the best approach will depend on the requirements of your application and the performance characteristics of the platform it’s running on.

Infinite scroll with scroll events (feat. throttle)

This method detects when the scroll reaches the end of the page and requests additional data to be loaded.

When the scroll has reached the bottom, the condition

total page height <= length of scrolled distance + current visible part is met and triggers the request for more data.

  • scrollTop: distance from the top of the page to the currently visible part
  • scrollHeight: total height of the page from top to bottom
  • clientHeight: height of the currently visible part excluding borders
  • offsetHeight: height of the currently visible part including borders

Checking the condition of scrollHeight and scrollTop + offsetHeight every scroll, event can cause excessive events and lead to performance degradation. To improve performance, throttle can be applied.

What is throttle?

Throttle is one of the ways to effectively control and improve performance by preventing excessive requests or processing that can lead to performance degradation!

Implementation Code

const onScroll = useCallback(() => {
if (!loadMore) return;
clearTimeout(timeoutId);
if (isEnd || isLoading) return;
timeoutId = setTimeout(() => {
if (
document.documentElement.scrollTop +
document.documentElement.clientHeight >=
document.documentElement.scrollHeight - 250
) {
loadMore();
}
}, 300);
}, [isEnd, isLoading, timeoutId, loadMore]);

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

Infinite scroll with Intersection Observer API

The Intersection Observer API observes the intersection between a browser viewport and a specified element. It allows you to subscribe to changes in whether a target element is currently visible or not on the screen.

In the context of implementing infinite scrolling, you would use Intersection Observer it to detect when the target element has entered the viewport, and then load more data to be added to the page between the existing content and the target element. This process would repeat as the user scrolls down the page.

Compared to traditional scroll events, using Intersection Observer for infinite scrolling is more performant and easier to use. With an scroll event, the function is executed every time the user scrolls, even if the element is not visible on the page. However, the function is only executed when the element enters or exits the viewport, reducing unnecessary function calls and improving performance.

API Methods and Options

// ✅ Options that can be applied to observation.
let options = {
root: null, // Determines "where" to execute the callback function when the target element enters. If null, the viewport is set as the root.
// root: document.querySelector('#scrollArea'), => You can also select a specific element.
rootMargin: '0px', // Allows you to expand the range by giving a margin value to the root.
threshold: 1.0 // Determines when to execute the callback function as the target element enters. If 1, the entire target element must be entered.
}

// ✅ The callback function to be executed when the target is observed.
let callback = () => {
console.log('Observed.');
}
// ✅ Declare the observer.
// The first argument is the callback function to be executed when the target is observed.
// The second argument specifies the observation options.
let observer = new IntersectionObserver(callback, options);
// ✅ Specify the target element.
// In React, use useRef to select the DOM element.
let target = useRef();
observer.observe(target); // ✅ Start observing the target element.
observer.unobserve(target); // ✅ Stop observing the target element.
  • isIntersecting

Boolean value that indicates whether the observed target is entering the root element and intersecting (true) or leaving the intersection state (false).

Implementation

import { useEffect, useState } from "react";

interface useIntersectionObserverProps {
root?: null;
rootMargin?: string;
threshold?: number;
onIntersect: IntersectionObserverCallback;
}
const useIntersectionObserver = ({
root,
rootMargin = "0px",
threshold = 0,
onIntersect
}: useIntersectionObserverProps) => {
const [target, setTarget] = useState<HTMLElement | null | undefined>(null);

useEffect(() => {
if (!target) return;
const observer: IntersectionObserver = new IntersectionObserver(
onIntersect,
{ root, rootMargin, threshold }
);

observer.observe(target);
return () => observer.unobserve(target);
}, [onIntersect, root, rootMargin, target, threshold]);

return { setTarget };
};

export default useIntersectionObserver;

The observe method starts observing the target element while unobserve stopping observing the target element. disconnect stops observing all elements being observed by the instance.

The observer detects when the user reaches the bottom of the page and loads a new list when the bottom value becomes true.

Implementing infinite scrolling with scroll events or an observer has a critical drawback. When scrolling down and accumulating 100 or 1000 data items, the browser DOM has to add 100 or 1000 new items, causing the browser to lag and negatively impacting the user experience.

Infinite Scroll with React Query

There are various ways to implement infinite scrolling, but in my project, I used the React Query library, which is designed to make infinite scrolling easy to implement with the Infinite observer.

Before implementing infinite scrolling, the backend API needs to be designed to easily determine whether the previous or next page exists, which can be anything like the next page number, page existence status, or the URL of the next page.

{
"isLast": false, // page existence status
"posts": [ ...postData ]
}

By receiving the boolean value of isLast, we can determine whether the current page is the last page.

useInfiniteQuery also takes in a query key, query function, and options just like useQuery, but the difference is that this query function takes an object parameter with a pageParam property.

Based on the value of this pageParam, useInfiniteQuery can send a request to the next page, and if it is the last page, we can set pageParam to undefined to prevent the request from being sent.

  • fetchNextPage - Function used to load the next page
  • hasNextPage - Function that determines if there is a next page
  • isFetchingNextPage - Loading state while loading the next page

Implementation

import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { PostsContainer } from "styles/Posts/PostStyle";
import PostCard from "pages/Post/PostCard";
import Loading from "components/Loading";
import ErrorPage from "components/ErrorPage";
import { useInfiniteQuery } from "react-query";
import axios from "axios";

const fetchPostList = async (pageParam) => {
const res = await axios.get(
`http://localhost:5001/posts?&page=${pageParam}&limit=6`);
const { posts, isLast } = res.data;
return { posts, nextPage: pageParam + 1, isLast };
};
function Posts() {
const { ref, inView } = useInView();
const { data, status, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
"posts",
({ pageParam = 1 }) => fetchPostList(pageParam),
{
getNextPageParam: (lastPage) =>
!lastPage.isLast ? lastPage.nextPage : undefined,
}
);
useEffect(() => {
if (inView) fetchNextPage();
}, [inView]);
if (status === "loading") return <Loading />;
if (status === "error") return <ErrorPage />;
return (
<>
<PostsContainer>
{data?.pages.map((page, index) => (
<React.Fragment key={index}>
{page.posts.map((post) => (
<PostCard key={post._id} post={post}></PostCard>
))}
</React.Fragment>
))}
</PostsContainer>
{isFetchingNextPage ? <Loading /> : <div ref={ref}></div>}
</>
);
}
export default Posts;

I used the react-intersection-observer library, which allows us to monitor whether a DOM element is visible on the screen or not using the inView property. This library is also used in the useInfiniteQuery example in the React Query official documentation.

By leveraging React-Query, I have successfully reduced page load times and decreased the amount of data retrieved per request, resulting in a more seamless browsing experience for users.

Overall, implementing infinite scroll functionality using React-Query and intersection-observer can greatly enhance the user experience by reducing page load times, increasing efficiency, and improving data retrieval.

I implemented infinite scroll in various ways then let’s see what are the pros and cons and when you should implement infinite scroll for your application.

Advantages of Infinite Scrolling

  • It allows for displaying content without the need to navigate to a separate page, providing a more seamless and natural experience for the user.
  • The primary advantage of infinite scrolling is its naturalness, which can encourage users to view more content.
  • It is well-suited for mobile devices.
  • This is because infinite scrolling allows for continuous scrolling through content using only up and down gestures, which is a basic function of mobile devices.

Disadvantages of Infinite Scrolling

  • One of the main disadvantages of infinite scrolling is that it can be difficult to remember the location of desired data.
  • Infinite scrolling makes it difficult to remember where specific information is located.
  • It can also be challenging to find the footer, as more data is loaded when reaching the bottom of the feed, causing the footer to be pushed out of view.

Important Consideration

What I’ve found while applying it to my project is that infinite scrolling can improve the user experience for certain types of websites, particularly those where users primarily consume content in a linear and chronological manner. However, it is important to carefully consider the potential downsides, such as slow load times and difficulty in accessing past data, before implementing infinite scrolling. It’s also important to consider the overall user experience and whether infinite scrolling fits well with the specific needs and goals of the website.

References

사용자에게 많은 데이터를 보여주는 방법 | 페이지 | 무한 스크롤

Intersection Observer — 요소의 가시성 관찰

https://uxplanet.org/ux-infinite-scrolling-vs-pagination-1030d29376f1

--

--