How to implement Infinite Scroll in React
Tags: react, javascript, hooks, intersectionobserver
Published: 2021-08-22 16:19:52

Infinite scrolling becoming more and more popular and we can see it in most of the applications, like LinkedIn, Facebook, Instagram, Youtube etc to name a few. So what exactly is "Infinite scrolling"? how to make an infinite scrolling view in react? Let's see. 

I'm Ramakrishna and I'm a full stack developer. I love to know "how" part of the solutions rather than just building them.

I was taking a front end challenge and that challenge was about to build a simple react application, similar to Netflix (Without that fancy UI). But the catch was to include Lazy loading on page content. So as I scroll horizontally / vertically the content should be lazily loaded.

### Why?
1. Lazy loading helps in application performance. User will be able to interact with the application much faster, as it loads only essential at the first render and will render other things as the user proceeds.
2. Browser load reduces. As the browser tries to load application in small small peices, it can render quickly and make the UX better. 
Coming back to the previous issue, so how to get the inifnite scrolling?

### How Infinite Scroll works?
To implement something, we need to understand how it works under the hood. 

So, as for the infinite scrolling, let's take FB as an example. An user might follow 1000s of friends and pages and might have millions of posts to watch. But loading that much posts will effect the performance. So for simplicity's sake, FB will load 10 posts on initial load. As user reaches the end of 10th post, it makes an async call to fetch next 10 posts. So as long as user scrolls, it fetches more and more posts. 

### React Implementation.
This implementation is done using a custom hook and IntersectionObserver. Let's dive into code. 

1. Custom Hook to fetch posts as user scrolls. 
```javascript
import { useState, useEffect, useCallback } from "react";
import axios from "axios";

function useFetch(page, url = '') {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [list, setList] = useState([]);
  const [formattedList, setFormattedList] = useState([]);

  const getPosts = useCallback(async () => {
    try {
      await setLoading(true);
      await setError(false);
      let res = []
      if(list && list.length === 0) {
      res = await axios.get(url);
      await setList(res.data)
      await setFormattedList([...res.data.slice(0, 10)])
     }
     else if (list.length > formattedList.length) {
        await setFormattedList([...formattedList, ...list.slice(formattedList.length, formattedList.length + 10)])
     }
      setLoading(false);
    } catch (err) {
      setError(err);
    }
  }, [page]);

  useEffect(() => {
    getPosts();
  }, [ getPosts, page]);

  return { loading, error, formattedList };
}

export default useFetch;
```
Above function is a custom hook to fetch data as per the scroll. Main thing to note here is, it's taking the url dynamically and makes a call only first time. And the url used here DOESN'T HAVE PAGINATION. So the hook is built in a way to handle splitting of result in progressive way. Find out more on useCallback [here](https://dmitripavlutin.com/dont-overuse-react-usecallback/)

Now, let's use the custom hook in our component. I have a custom component, which list's albums on each row, and each album will have multiple songs.

2. App.js component which uses custom hook for infinite scroll.
```Javascript
import './App.css';
import React, { Suspense, useState, useRef, useEffect, useCallback } from "react";
import useFetch from "./utils/customFetch";
import { AlbumList } from './components';

const App = () => {

const [page, setPage] = useState(1);
const { loading, error, formattedList = [] } = useFetch(page, 'https://jsonplaceholder.typicode.com/albums');

const row = useRef(null);

const handleObserver = useCallback((entries) => {
  const target = entries[0];
  if (target.isIntersecting) {
    setPage((prev) => prev + 1);
  }
}, []);

useEffect(() => {
  const option = {
    root: null,
    rootMargin: "20px",
    threshold: 0
  };
  const observer = new IntersectionObserver(handleObserver, option);
  if (row.current) observer.observe(row.current);
}, [handleObserver]);

  return (
    <div className="App">
      <Suspense fallback={<div>Loading</div>}>
        <AlbumList label="Component 1" albums={formattedList} />
      </Suspense>
      {loading && <p>Loading...</p>}
      {error && <p>Error!</p>}
      <div ref={row} />
    </div>
  );
}

export default App;

```
Here, I'm loading an AlbumList custom component which will fetch 10 albums on initial load. an empty div is put after AlbumList (To mark the end of current view / page). The ref is used inside IntersectionObserver to listen to when the scroll position reached this div. If the Observer detects intersection, it calls the customHook to fetch next 10 albums. 
Like this, this custom hook can be used everywhere we need to make an infinite scrolling. Read more about IntersectionObserver [here](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) and [here](https://dev.to/producthackers/intersection-observer-using-react-49ko)

My github repo link is [here](https://github.com/kgrvaidya/k-bee-react.git), which has the complete running version of the infinite scrolling.  
Feel free to put feedback :)

### References
1. [Infinite Scroll using React](https://medium.com/suyeonme/react-how-to-implement-an-infinite-scroll-749003e9896a)
2. [Infinite Scroll Image](https://www.google.com/url?sa=i&url=https%3A%2F%2Fblog.logrocket.com%2Finfinite-scroll-techniques-in-react-adcfd7ff32bd%2F&psig=AOvVaw2NvspI9OhF0rbwVls-BIG_&ust=1629735387266000&source=images&cd=vfe&ved=0CAsQjRxqFwoTCJjVicuDxfICFQAAAAAdAAAAABAD)