2024 Guide to Infinite Scrolling in React/Next.js
Tags: nextjs, tutorial, react, frontend
Published: 2024-06-05 17:00:43

Ever saw how posts keep on displaying on the feed section of X (Twitter) or Instagram if you keep scrolling down?

That “thing” is called infinite scroll.

## What is Infinite Scrolling?

Infinite scrolling is a technique or approach in web development where more resources are fetched and displayed on the page as the user scrolls down.

For example, in social media platforms such as X (Twitter), if you scroll down in the feed, more posts will display to you.

This approach is helpful when you have a lot of resources to show on the page and you do not want to fetch them all at once and slow down the application.

## How to use it in React/Next.js?
Of course, you can build your own infinite scrolling component from scratch but in many cases, it is better to use a library.

On [Libertas](https://libertas-vert.vercel.app/), I have used a popular library called react-infinite-scroll-component.

This library has an inbuilt component which does the behaviour of infinite scrolling for us. We just have to dump our code of fetching multiple items inside this code.

> Feedback is appreciated about this approach.

## Understanding the Scenario
I have a [feed page](https://libertas-vert.vercel.app/feed) on Libertas where I show all the posts that the users have created. It is just like how different items are displayed on the feed page on Reddit, X, Medium, Nike and other such platforms.

Our aim is to fetch some posts/items when the page loads fully. When the user scrolls to the bottom of the page or the last item that was fetched when the page loaded, we trigger a GET request to fetch more posts.

This process repeats until all the items are fetched, which may not be possible if the items are huge in numbers (>500 items). Hence, the name “Infinite scroll”! Users will get exhausted, won’t they?

## Steps to Implement `react-infinite-scroll-component`
Below are the steps where I mention how I added this component into Libertas.

### Install the package
**For npm:**

```
npm i react-infinite-scroll-component
```
**For yarn:**

```
yarn add react-infinite-scroll-component
```
Copy and paste the below code for infinite scroll in Libertas

```
<InfiniteScroll
  dataLength={items.length} //This is important field to render the next data
  next={fetchData}
  hasMore={true}
  loader={<h4>Loading...</h4>}
  endMessage={
    <p style={{ textAlign: 'center' }}>
      <b>Yay! You have seen it all</b>
    </p>
  }
>
  {items}
</InfiniteScroll>
```

### Understanding the props
According to `react-infinite-scroll-component` [GitHub page](https://github.com/ankeetmaini/react-infinite-scroll-component):

> **dataLength:** set the length of the data. This will unlock the subsequent calls to next.
> **next:** a function which must be called after reaching the bottom. It must trigger some sort of action which fetches the next data. The data is passed as children to the `InfiniteScroll`component and the data should contain previous items too. e.g. Initial `data = [1, 2, 3]` and then next load of data should be `[1, 2, 3, 4, 5, 6]`.
> **hasMore:** it tells the `InfiniteScroll` component on whether to call next function on reaching the bottom and shows an `endMessage` to the user
> **loader:** you can send a loader component to show while the component waits for the next load of data. e.g. `<h3>Loading…</h3>` or any fancy loader element
> **endMessage:** this message is shown to the user when he has seen all the records which means he’s at the bottom and hasMore is false
> **items:** The list of items to be fetched and displayed on the page. In case of Libertas, the `{items}` are the list of posts created by users.

### Further Steps:
1. Fetch some posts when the page loads
2. Display the list of posts on the page
3. Make a slight change on the API GET request (Backend) to fetch the next list of posts
4. Fetch the next list of posts on the frontend

### Final code:
  

```
const [posts, setPosts] = useState([]);
  const [count, setCount] = useState(4);
  const [currentCount, setCurrentCount] = useState(null);
  const [total, setTotal] = useState(null);

  const fetchPosts = async () => {
    const data = await fetchAllPosts(count);
    console.log(data?.data?.data);

    setPosts(data?.data?.data);
    setCurrentCount(data?.data?.currentLength);
    setTotal(data?.data?.total);
    setCount(count + 2);
  };

  useEffect(() => {
    fetchPosts();
  }, []);

  return (
    <Stack>
        <InfiniteScroll
          dataLength={posts?.length} //This is important field to render the next data
          next={fetchPosts}
          hasMore={currentCount !== total}
          loader={<h4>Loading...</h4>}
          endMessage={
            <p style={{ textAlign: "center" }}>
              <b>Yay! You have seen it all</b>
            </p>
          }
        >
          {posts.map((post) => (
            <PostComponent
              key={post._id}
              post={post}
              id={post._id}
              handleUpvote={() => handleVote(upvoteAPost, post._id)}
              handleDownvote={() => handleVote(downvoteAPost, post._id)}
              individualView={false}
            />
          ))}
        </InfiniteScroll>
      </Stack>
  );
```
### Breakdown of the code
I created a GET request named `fetchAllPosts `which fetches 4 posts when the page loads. I used map function to list all the posts inside the `InfiniteScroll `component.

In order to fetch the next set of posts, I had to make a slight change in the API of the request (written in Node.js) where I fetch the posts. That change was adding a count or a number that will tell me how many posts to fetch. When the user scrolls down the web page, those number of posts will be fetched from the database and displayed.

```
// @desc    Get all the posts
// @route   GET /api/user/posts
// @access  Public
const getAllPosts = asyncHandler(async (req, res, next) => {
  const { count } = req.query;

  const posts = await PostModel.find();
  const postsToDisplay = posts.reverse().slice(0, count);

  res.status(200).json({
    success: true,
    total: posts.length,
    currentLength: postsToDisplay.length,
    data: postsToDisplay,
  });
});
```
I added a count variable which comes from the query in the URL just like this: `api/v1/posts?count=4`. This fetches only 4 posts.

3. Now, things were easier. Time to add the frontend code to fetch the next set of posts.

  

```
const [posts, setPosts] = useState([]);
  const [count, setCount] = useState(4);
  const [currentCount, setCurrentCount] = useState(null);
  const [total, setTotal] = useState(null);

  const fetchPosts = async () => {
    const data = await fetchAllPosts(count);

    setPosts(data?.data?.data);
    setCurrentCount(data?.data?.currentLength);
    setTotal(data?.data?.total);
    setCount(count + 2);
  };
```
The above code first fetches 4 posts (coming from the initial value of the count state variable).

It also sets the current count or the current length of the posts which will be 4 on the first fetch.

The value of total no. of posts is also set as it is being returned in the API response.

Lastly, the value of count updates to 6, which will be used for the next fetch function call.

The above values are used in the `InfiniteScroll `component below:

```
<InfiniteScroll
  dataLength={posts?.length} //This is important field to render the next data
  next={fetchPosts}
  hasMore={currentCount !== total}
  loader={<h4>Loading...</h4>}
  endMessage={
    <p style={{ textAlign: "center" }}>
      <b>Yay! You have seen it all</b>
    </p>
  }
>
  {posts.map((post) => (
    <PostComponent
      key={post._id}
      post={post}
      id={post._id}
      handleUpvote={() => handleVote(upvoteAPost, post._id)}
      handleDownvote={() => handleVote(downvoteAPost, post._id)}
      individualView={false}
    />
  ))}
</InfiniteScroll>
```
### Working of `InfiniteScroll `component
In the above code, the list of posts are displayed. After this:

- The value of `dataLength `gets assigned to the number of posts fetched for the first time.
- When the user scrolls down, the `hasMore `prop comes into action.
`hasMore `tells us that if it is true, initiate the function available in the next prop. In our code, when the current count or current length of the posts is not equal to the total no. of posts, call the `fetchPosts `function.
- Then `fetchPosts `is called with the updated count value of 6, then 6 posts are fetched and the value of posts array is updated. The value of `dataLength `is also updated.
- If all the posts are not fetched, the content inside the loader prop is displayed after the latest post. Ideally, this can be a [skeleton component](https://www.smashingmagazine.com/2020/04/skeleton-screens-react/) or a loading text/animation.
- If `hasMore `is false or in other words, all the posts are fetched from the database, the element inside the `endMessage `prop is displayed after the last post.

## Conclusion
**Infinite scroll** is a good approach to make a website perform a bit better but like everything in tech, this approach is not for every website. It mostly depends on the type of platform and the type of users (target audience).

I added this to [Libertas](https://medium.com/@sumsourabh14/reddit-alternative-heres-what-i-am-building-sort-of-2b8044842952), the online discussion platform built with Next.js, because the users will create multiple posts and it will be a headache/nightmare to render everything at once on a single web page. Wouldn’t it?

## Wondering what is Libertas?
**Libertas is an online discussion website** for users where they can just discuss. **Discuss freely. No rules, no moderators.**

You can just look at the posts that the users have created or if you feel hooked, you can sign up and start creating! You can upvote/downvote posts and comment on them. If you forget your password, there is an option reset it too!

[Try Libertas here.](https://libertas-vert.vercel.app/)

You can let me know on [LinkedIn](https://www.linkedin.com/in/sumansourabh14/) or [Twitter](https://twitter.com/sumansourabh48) if you want any feature or would like to have a suggestion.

**Any feedback is deeply appreciated!**