Create Infinite scroll in React
Tags: react, programming, beginners, javascript
Published: 2022-03-05 12:28:20

## Components

There are mainly three components of infinite scroll. Fetching data from the paginated api, 

Maintaining the data state on the website and detecting user scroll. 

## Fetching

You can do fetching with [Fetch Api](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or [Axios](https://www.npmjs.com/package/axios). Your api should have pagination. 

In this blog we are going to use the `fetch` API. 

## State management

You can start with using react `useState`. You might want to persist data in local storage or have more complex state management with libraries such as Recoil, Redux , Zustand etc.

## Detecting user scroll 👀

Have a loading component at the end of you list. If the loading component is in view, we will fetch  more data. If we have reached the last page of paginated  api , we will stop fetching. 

We will use `react-infinite-scroll-hook` in this blog. 

There are other ways to do the same. Here are some : 

- Using the [Intersection observable api](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
- Create your own custom hook with `useRef`  [Go here](https://stackoverflow.com/questions/45514676/react-check-if-element-is-visible-in-dom)
- react-in-vew [here](https://www.npmjs.com/package/react-in-viewport)
- react-cool-inview [here](https://www.npmjs.com/package/react-cool-inview)

---

### Code Repo

- Github :

[infinite-scroll-react/infinite-scroll-with-react at master · pratiksharm/infinite-scroll-react](https://github.com/pratiksharm/infinite-scroll-react/tree/master/infinite-scroll-with-react)

---

## ⚙️ How does this works?

Infinite scrolling  works in much the same way that normal website browsing works, behind the scenes. Your browser requests some content, and a web server sends it back.

Infinite scroll often works automatically, loading new content when the reader reaches the bottom of the page or close to it. But there are also compromises. Some sites present a **load more** button at the bottom of their content. This still uses the same underlying technique to inject more content, but it acts manually instead.

Infinite scroll works in a very simple way. Fetch more data when the user is at the bottom of the webpage. 

Usually here is how we do fetching in react.

```jsx
const [data, setData] = React.useState([])

const fetcher = async(url) => {
	const res = await fetch(url)
  setData(res.body.items);
}

useEffect(() => {
  
  fetcher(url)

},[data])
```

When a user scrolls done at the bottom of the page. If the Loader component is in view of the user screen, we will fetch more data. The Loader component is at the last of the list view and will be send at the bottom, thus not in view, not fetching more data.

We will be using the [Github’s users api](https://docs.github.com/rest) . You can use any api which have pagination. There are two types of paginations that are mainly used.

- Offset Pagination
- Cursor-based pagination

You can find references at the bottom of the page to read more about them. 

Let’s add more State and change the fetcher function to support pagination. 

```jsx
const [data, setData] = React.useState([])

const [since, setSince] = useState(0);     // ✅

const [limit, setLimit] = useState(10);    // ✅

const [loading, setLoading] = useState(false); // ✅

const fetcher = async (url) => {
	  setSince(since + limit);
    const response = await fetch(`https://api.github.com/users?since=${since}&per_page=${limit}`);
    const json = await response.json();
    setData((data) => [...data, ...json]);
}

useEffect(() => {
  
  fetcher(url)

},[data, loading]) // Maybe add since and limit here as well 🥳
```

We will Toggle the `loading` state, so that  we can fetch more data. We are also incrementing the `since` state by `limit` i.e. 10. 

---

## Code Walkthrough

<aside>
📌  i am also planning to write my next blog on how to make a paginated api with `prisma`,  `nextjs` and `postgres` . So, if you are interested maybe follow me 🙌🏻.

</aside>

### Setup

Go ahead open vscode, in the terminal . 

run `npx create-react-app` in our terminal.

```jsx
npx create-react-app infinite-scroll
```

--- 

### Styles

add a bit of styles with good old css in the `app.css` file. Create a classname of `.main` for the list view and a `.item` for our items. 

```css
.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.item {
  display: flex;
  width: 300px;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 30px;
  border-bottom: 2px solid #eaeaea;
}
```

Here is how our `src/app.js` would look like : 

```jsx
import { useState } from 'react';
import './App.css';

function App() {

  return (
    <div className="App">
      <h2>List of github users</h2>
      <main className='main'>

				<div className="loader">
				          <h1>Loading...</h1>
	        </div>

      </main>
    
    </div>
  );
}

export default App;
```

### States

We will have a few  `useState` . 

- Data ⇒ so that we can store data
- since ⇒ offset for pagination
- limit ⇒ the number of list items per page.
- Loading ⇒ Loading element will be used for fetching. If it is `true`, then we will fetch more and if `false`, not fetching.
- hasNextPage ⇒ For stopping the fetching when there are no more pages. or data from the api.

```jsx
import { useState } from 'react';
import './App.css';

function App() {
	const [data, setData] = useState([]);
	
	const [since, setSince] = useState(0);
	const [limit, setLimit] = useState(10);
	
	const [loading, setLoading] = useState(false);

	const [hasNextPage, setHasNextPage] = useState(true);

return (
			// like above
)}

export default App;
```

### Fetch function

```jsx
const fetchmore = async (since) => {
  setLoading(true)
  setSince(since + limit);
  try {
    const response = await fetch(`https://api.github.com/users?since=${since}&per_page=${limit}`);
    const json = await response.json();
    setData((data) => [...data, ...json]);
  }
  catch(e) {
    console.log(e);
		return setHasNextPage(false);
  }
  finally {
    setLoading(false);
  } 
}
```

`fetchmore` will run whenever the loader component is in view. 

Then we have a `setSince`  which will set the number of offset that we want. For example in the first fetch request  since value is 0, limit  = 10, ⇒ fetching the first 10 users of Github. Similarly, in the second fetch request we will get the next 10 users of Github. 

`setData`  is storing all the data that we are fetching and we can render the `data` state in the JSX. So let’s do that. 

```jsx
return (
    <div className="App">
      <h2>List of github users</h2>
      <main className='main'>

      {data && data.map((item, index) => {
          return (
            <div key={index} className='item'>
              <p>{item && item.login }</p>
              <img src={item.avatar_url} width={100} height={100} alt={item.avatar_url} />
            </div>
          )
        })}
        {
          (loading || hasNextPage) && 
          <div className="loader" >
          <h1>Loading...</h1>
        </div>
        }

      </main>
     
     
    </div>
  );
```

Loader component will always be at the bottom inside the `main` Dom element. 

### Loader component

If you look at the last coding block we added a loader component. It looks like this

```jsx
 {
          (loading || hasNextPage) && 
          <div className="loader" >
          <h1>Loading...</h1>
        </div>
        }
```

For detecting this component is in view or not we will use the   `react-infinite-scroll-hook`  . The hook provides pretty much everything that we will need for creating infinite-scroll. It uses the Observable Api to detect if the component is in view or not. 

```jsx
npm install react-infinite-scroll-hook 
```

Updating the `app.jsx` . Our component will look like this.

```jsx
import { useState } from 'react';
import './App.css';

import useInfiniteScroll from 'react-infinite-scroll-hook';

function App() {
  const [data, setData] = useState([]);

  const [since, setSince] = useState(0);
  const [limit, setLimit] = useState(10);

  const [loading, setLoading] = useState(false);

  const [hasNextPage, setHasNextPage] = useState(true);

  const fetchmore = async (since) => {
    
    setLoading(true)
    setSince(since + limit);
    try {
      const response = await fetch(`https://api.github.com/users?since=${since}&per_page=${limit}`);
      const json = await response.json();
     return  setData((data) => [...data, ...json]);
    }
    catch(e) {
      console.log(e);
      return setHasNextPage(false);
    }
    finally {
     return  setLoading(false);
    }
    
  }

  const [sentryRef] = useInfiniteScroll({
    loading, 
    hasNextPage: hasNextPage ,
    delayInMs:500,
    onLoadMore: () => {
      fetchmore(since);
    }
  })

  return (
    <div className="App">
      <h2>List of github users</h2>
      <main className='main'>
      {data && data.map((item, index) => {
          return (
            <div key={index} className='item'>
              <p>{item && item.login }</p>
              <img src={item.avatar_url} width={100} height={100} alt={item.avatar_url} />
            </div>
          )
        })}
        {
          (loading || hasNextPage) && 
          <div className="loader" ref={sentryRef}>
          <h1>Loading...</h1>
        </div>
        }

      </main>
     
     
    </div>
  );
}

export default App;
```

Let’s look at who the hook will work. 

```jsx
const [sentryRef] = useInfiniteScroll({
    loading, 
    hasNextPage: hasNextPage ,
    delayInMs:500,
    onLoadMore: () => {
      fetchmore(since);
    }
  })
return ({ (loading || hasNextPage) && 
          <div className="loader" ref={sentryRef}>
          <h1>Loading...</h1>
        </div>
});
```

Set the `sentryRef` to the loader component. This way the hook will detect if the component is in view or not. 

`onLoadMore`  will run whenever the loader component is in view. We provide `fetchmore`  which will fetch more `data` . 

`delayInMs`  is the delay we want before running  `onLoadMore`  . 

For error handling you can also use  `disabled` . It will stop the hook. 

```jsx
const [isError, setIsError] = useState(false);

const fetchmore = async (since) => {
    setLoading(true)
    setSince(since + limit);
    try {
      const response = await fetch(`https://api.github.com/users?since=${since}&per_page=${limit}`);
      const json = await response.json();
     return  setData((data) => [...data, ...json]);
    }
    catch(e) {
      console.log(e);
      setIsError(true);
      return setHasNextPage(false);
    }
    finally {
     return  setLoading(false);
    }
    
  }

const [sentryRef] = useInfiniteScroll({
    loading, 
    hasNextPage: hasNextPage ,
    delayInMs:500,
		disabled: isError,
    onLoadMore: () => {
      fetchmore(since);
    }
  })
return ({ (loading || hasNextPage) && 
          <div className="loader" ref={sentryRef}>
          <h1>Loading...</h1>
        </div>
});
```

This is pretty much it. 

If I have done anything wrong do let me know in the comments.

Feedbacks are appreciated ✨.

If you face any error or maybe wanna say hi ✋🏻. Feel free to dm me. 👇🏻

- Social Media
    
    Twitter ⇒ [@biomathcode](https://twitter.com/BioMathCode)
    

---

### Next Blog

<aside>
📌 Create Paginated Rest api with prisma, next.js and postgres.

</aside>

## References

1. [Pagination prisma](https://www.prisma.io/docs/concepts/components/prisma-client/pagination) 
2. [Pagination at slack](https://slack.engineering/evolving-api-pagination-at-slack/)
3. [react-infinite-scroll-hook](https://www.npmjs.com/package/react-infinite-scroll-hook)