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.
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 -
- React Hooks -
useState
anduseEffect
hooks. - Fetch data with
Axios
orfetch
API ( we will use Axios in this blog ). - Intersection Observer API,
useRef
anduseCallback
(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
The first step will be to import the react hooks and Axios
import axios from "axios"; import { useCallback, useEffect, useRef, useState } from "react";
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);
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.Now we will set up an effect hook
useEffect
to call thefetchMovies
when the pageNumber gets changed. We are doing this because we are going to increase thepageNumber
whenever we intersect the loading element.// fetching data useEffect(() => { fetchMovies(); }, [fetchMovies]);
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 ofuseEffect
. That's all for functionality now let's will continue with UI.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.