The Concept of Infinite Scroll
Infinite scroll eliminates the need for users to manually click “next” or “load more” buttons to see additional content. Instead, new content is automatically fetched and appended to the existing content when the user scrolls to the bottom of the page.
Key Components of Infinite Scroll
- State Management: To track the current page, total pages, blog posts, and fetching state.
- Data Fetching: To retrieve the content from the server.
- Intersection Observer: To detect when the user has scrolled to the bottom of the page.
Step-by-Step Implementation
1. Setting Up the Component
We’ll start by setting up the BlogLists component. This component will use the useFetch hook to fetch blog posts from an API and manage the state using React hooks.
import React, { useContext, useState, useEffect, useRef, useCallback } from 'react';
import useFetch from '../../hooks/useFetch';
import { StoreContext } from '../../context/StoreContext';
import styles from './BlogList.module.css';
import Loading from '../Loading/Loading';
import { Link } from 'react-router-dom';
import { IoMdPricetag } from 'react-icons/io';
const BlogLists = () => {
const { main_site } = useContext(StoreContext);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [blogPosts, setBlogPosts] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const loader = useRef(null);
const { data, total_pages, loading, error } = useFetch(`${main_site}/wp/v2/posts?page=${currentPage}&per_page=10`);
useEffect(() => {
if (data && total_pages) {
setBlogPosts(prevPosts => [...prevPosts, ...data]);
setTotalPages(total_pages);
setIsFetching(false); // Fetching is done
}
}, [data, total_pages]);
// Continue...
Handling the Observer
We use the Intersection Observer API to detect when the user has reached the bottom of the page. When the observer detects that the loader element is in view, we increment the current page to fetch more posts.
const handleObserver = useCallback((entries) => {
const target = entries[0];
if (target.isIntersecting && !isFetching && currentPage < totalPages) {
setIsFetching(true); // Set fetching state
setCurrentPage(prevPage => prevPage + 1);
}
}, [isFetching, currentPage, totalPages]);
useEffect(() => {
const observer = new IntersectionObserver(handleObserver, { threshold: 1.0 });
if (loader.current) observer.observe(loader.current);
return () => {
if (loader.current) observer.unobserve(loader.current);
};
}, [handleObserver]);
//Continue...
Rendering the Component
We render the blog posts along with a loading indicator. The loader element at the bottom of the page is observed by the Intersection Observer to trigger loading more posts.
if (error) {
return <Loading state={true} />;
}
return (
<>
<section className="blogs">
<div className={`wrapper ${styles.blogs__list__wrapper}`}>
<div className={styles.blogs__grid}>
{blogPosts && blogPosts.length > 0 ? (
blogPosts.map(post => (
<div className={styles.blog__item} key={post.id}>
<time className={styles.blog_date} dateTime={new Date(post.date).toISOString()}>
{new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
</time>
<Link to={post.slug}>
<h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
</Link>
<div className="generic-content" dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
<Link to={post.slug} className={`${styles.blog__link} underline`}>Continue reading »</Link>
</div>
))
) : (
loading && <Loading state={false} />
)}
</div>
</div>
<div className="wrapper">
{loading && <Loading state={false} />}
<div ref={loader} />
</div>
</section>
</>
);
};
export default BlogLists;
Infinite Loop Prevention
One critical aspect of implementing infinite scroll is to ensure it doesn’t result in an infinite loop. In our case, we handle this by:
- Checking if fetching is already in progress (
!isFetching). - Ensuring the current page is less than the total pages (
currentPage < totalPages).
This logic ensures that new requests are only made when necessary, preventing unnecessary network calls and potential infinite loops.