Building An Infinite Scroll  Into Your React App

Building An Infinite Scroll Into Your React App

·

8 min read

Introduction

Infinite scroll has become a major feature in apps we use in our day to day life like Twitter Instagram and just generally content feed apps that just want your undivided attention daily, on a functionality point of view infinite scroll outperforms pagination approaches to loading data due to it being seamless to the user and only loads more data when the user reaches the end of the scroll.

Infinite scroll

Infinite scroll is a feature where data is loaded onto the user’s page when the user reaches the end or almost the end of the scroll page, this is done by calling a paginated API, A paginated API for reference is an API that returns a list of data whenever we call the API and can return different sets of data based on the page count that we passed into it an example of a paginated API would be the API we use in this example

`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`

page is the variable we pass into the API it is going to be a number that we track and increment after loading every page.

Infinite scroll although an excellent approach to loading data isn't the most optimal for all projects, some projects do function better with pagination, but infinite scrolling works best when loading related data that is loaded in a preferably chronological order based on time or relevance, pagination though is useful when users need to load data that far back, let's say you have some bank transaction records and you know that the records are a month away you can skip to the farthest page and circle back if you overshoot the page, but in reality, infinite scroll and a good date filter can solve that problem

Prerequisite

Building this application would require some basic knowledge of a couple of things we would be using in our application.

  • React
  • Javascript
  • REST API’s

Implementation

In react we have 2 options to implement infinite scroll in our app.

  • Using an exciting library (The smart boring way)
  • Implement the infinite scroll (The fun slow way)

Using an exciting library (The smart boring way)

A fast way to implement infinite scrolling in react would be to use a third-party library one of my go-to libraries for this feature would be the react-infinite-scroll-component.

react-infinite-scroll-component is a simple library that exports an <InfiniteScroll/> component that can be used in our application and its feature-rich with props and events you can call before and after loading more data into the app, also a cool thing would be a refresh function you can call whenever you want to load new data to the top of your table.

####Installing

 npm install --save react-infinite-scroll-component

or

yarn add react-infinite-scroll-component

In our App.jsx

 import React from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import axios from "axios";

let page = 1;
const fetchData = (setItems, items) => {
 axios
   .get(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`)
   .then((res) => {
     setItems([...items, ...res.data]);
     page = page + 1;
   });
};

const refresh = (setItems) => {};

export default function App() {
 const [items, setItems] = React.useState([]);

 React.useEffect(()=>{
   fetchData(setItems,items)
 },[])
 return (
   <InfiniteScroll
     dataLength={items.length} //This is important field to render the next data
     next={() => {
       fetchData(setItems, items);
     }}
     hasMore={true}
     loader={<h4>Loading...</h4>}
     endMessage={
       <p style={{ textAlign: "center" }}>
         <b>Yay! You have seen it all</b>
       </p>
     }
     // below props only if you need pull down functionality
     refreshFunction={refresh}
     pullDownToRefresh
     pullDownToRefreshThreshold={50}
     pullDownToRefreshContent={
       <h3 style={{ textAlign: "center" }}>&#8595; Pull down to refresh</h3>
     }
     releaseToRefreshContent={
       <h3 style={{ textAlign: "center" }}>&#8593; Release to refresh</h3>
     }
   >
     <div style={{ minHeight: "100vh" }}>
       {items.map((user) => (
         <img src={user.url} height="100px" width="200px" />
       ))}
     </div>
   </InfiniteScroll>
 );
}

Let's break our code down into smaller bits.

let page = 1;
const fetchData = (setItems, items) => {
 axios
   .get(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`)
   .then((res) => {
     setItems([...items, ...res.data]);
     page = page + 1;
   });
};

The fetch function is able to call our API to get new data it's triggered by the <InfiniteScroll/> component when we scroll to the end of the view, there is a count variable we use to monitor the page loaded and it is incremented after the data is loaded.

 const [items, setItems] = React.useState([]);
 React.useEffect(()=>{
   fetchData(setItems,items)
 },[])

React effect is used to load the first batch of data into the view, we pass the systems function and the items variable into the function (something new I should have been doing a while back to remove API calls from my component)

<InfiniteScroll
     dataLength={items.length} //This is important field to render the next data
     next={() => {
       fetchData(setItems, items);
     }}
     hasMore={true}>
/////// 

/// code
///////
>
     <div style={{ minHeight: "100vh" }}>
       {items.map((user) => (
         <img src={user.url} height="100px" width="200px" />
       ))}
     </div>
   </InfiniteScroll>

We call our component and pass data into it if you need documentation you can check it out here https://www.npmjs.com/package/react-infinite-scroll-component.

Here is the output.

Screen Shot 2022-05-22 at 4.05.48 PM.png

Implement the infinite scroll (The fun way)

Implementing a scroll component can be a nice learning project and gives you more control than when you use a component and is pretty easy to set up but can take a bit of time to perform research on how to get it done, luckily I've done that for you.

Pros of using custom components

  • Customizable
  • Very light since its just one component

Cons

  • Takes a bit of time to set up
  • It May is not as robust as an already built component

Here is our codebase

import React, { Component } from "react";

class ScrollComponent extends Component {
 constructor() {
   super();
   this.state = {
     loading: false,
     page: 0,
     prevY: 0
   };
 }

 async getItems() {
   try {
     await this.props.loadData();
   } catch (error) {
     console.log(error);
   }
 }

 componentDidMount() {
   this.getItems();

   var options = {
     root: null,
     rootMargin: "0px",
     threshold: 1.0
   };

   this.observer = new IntersectionObserver(
     this.handleObserver.bind(this),
     options
   );
   this.observer.observe(this.loadingRef);
 }

 async handleObserver(entities, observer) {
   const y = entities[0].boundingClientRect.y;
   if (this.state.prevY > y) {
     this.setState({ loading: true });
     console.log(this.state);

     await this.getItems();

     this.setState({ loading: false });
     console.log(this.state);
   }
   this.setState({ prevY: y });
 }

 render() {
   // Additional css
   const loadingCSS = {
     height: "100px",
     margin: "30px"
   };

   // To change the loading icon behavior
   const loadingTextCSS = { display: this.state.loading ? "block" : "none" };

   return (
     <div className="container">
       <div style={{ minHeight: "800px" }}>
         {/* {this.state.photos.map(user => (
          <img src={user.url} height="100px" width="200px" />
        ))} */}
         {this.props.children}
       </div>
       <div
         className="house"
         ref={(loadingRef) => (this.loadingRef = loadingRef)}
         style={loadingCSS}
       >
         <span style={loadingTextCSS}>Loading...</span>
       </div>
     </div>
   );
 }
}

export default ScrollComponent;

And in our app.jsx component we replace the <InfiniteScroll/> and insert our new component.

import React from "react";
import axios from "axios";
import ScrollComponent from "./scroll";

let page = 1;
const fetchData = async (setItems, items) => {
 const data = await axios.get(
   `https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`
 );

 setItems([...items, ...data.data]);
 page = page + 1;
};

const refresh = (setItems) => {};

export default function App() {
 const [items, setItems] = React.useState([]);

 React.useEffect(() => {
   fetchData(setItems, items);
 }, []);
 return (
   <ScrollComponent
     loadData={() => {
       fetchData(setItems, items);
     }}
   >
     <div style={{ minHeight: "100vh" }}>
       {items.map((user) => (
         <img
           key={Math.random()}
           src={user.url}
           height="100px"
           width="200px"
         />
       ))}
     </div>
   </ScrollComponent>
 );
}

Let's break our component down into smaller bits so we can understand it.

Part 1
 componentDidMount() {
   this.getItems();

   var options = {
     root: null,
     rootMargin: "0px",
     threshold: 1.0
   };

   this.observer = new IntersectionObserver(
     this.handleObserver.bind(this),
     options
   );
   this.observer.observe(this.loadingRef);
 }

Our componentDidMount function is run as soon as our component is started and adds an IntersectionObserver observer to the component that checks out the house and measures the difference between it and the this.props.children and calls the handleObserver function when the observer is triggered.

 async handleObserver(entities, observer) {
   const y = entities[0].boundingClientRect.y;
   if (this.state.prevY > y) {
     this.setState({ loading: true });
     console.log(this.state);

     await this.getItems();

     this.setState({ loading: false });
     console.log(this.state);
   }
   this.setState({ prevY: y });
 }

Our handleObserver example function calls out the update function that's passed into the props, this is powerful because we can use the concept of dependency injection to pass in the update function from our component making this component agnostic to its use case

 const [items, setItems] = React.useState([]);

 React.useEffect(() => {
   fetchData(setItems, items);
 }, []);

We take advantage of react useEffect to set up how we manage data in our component, we need to pass setItems and items into the fetchdata component to pass control initoo the function,

 render() {
   // Additional css
   const loadingCSS = {
     height: "100px",
     margin: "30px"
   };

   // To change the loading icon behavior
   const loadingTextCSS = { display: this.state.loading ? "block" : "none" };

   return (
     <div className="container">
       <div style={{ minHeight: "800px" }}>
         {/* {this.state.photos.map(user => (
           <img src={user.url} height="100px" width="200px" />
         ))} */}
         {this.props.children}
       </div>
       <div
Class = ‘house’
         ref={(loadingRef) => (this.loadingRef = loadingRef)}
         style={loadingCSS}
       >
         <span style={loadingTextCSS}>Loading...</span>
       </div>
     </div>
   );
 }

Our render function renders our child component passed into the component, this lets us reuse our component for different types of use cases.

Replacing our component in the App.js

 <ScrollComponent loadData={()=>{
     fetchData(setItems, items);
   }}>
     <div style={{ minHeight: "100vh" }}>
       {items.map((user) => (
         <img src={user.url} height="100px" width="200px" />
       ))}
     </div>
   </ScrollComponent>

Our output(similar to our old implementation).

Screen Shot 2022-05-22 at 4.05.48 PM.png

Conclusion

Infinite scrolling is becoming an amazing way of showing feed data because it offers a nonstop flow of data that is addictive (talking from a users point of view) and only loads new data when it reaches the end of the page, this is done by monitoring the page count and incrementing the page seen page at the end of every load.

In this guide, we learned 2 different modes of implementing this feature in react,

  • Using an exciting library (The smart boring way)
  • Implement the infinite scroll (The fun way)

Each approach gives the same result but comes with different pros and cons that make them perfect for different situations, I personally keep a copy of my own custom component on my PC and copy the custom component into my new project, it helps to keep it flexible for different project seeing as it is just a component and can be called whenever it's needed, also the concept of injecting the load function makes it easy to use and re-use across projects.

I hope this article was helpful to you, cheers and till next time!

Referrence

Originally written by King Somto for JavaScript Works