Full Stack Instagram: Infinite Scroll
Tags: javascript, react, webdev, firebase
Published: 2021-05-15 05:44:35

This is my favorite project extension because this feature significantly improves the user experience in the app. Instead of clicking the "next" button, the user can scroll infinitely (as long as there's additional data in the database) to see more posts.

## Table of Contents

- Demo
- Overview
- Enable scroll trigger through styling
- Post fetching
- Conclusion

## Demo

You can check on the full source code and try them in Replit.

Repl URL: [https://replit.com/@arnoldschan/PostPagination](https://replit.com/@arnoldschan/PostPagination)


![paginate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ex8bewrq2qled4avfic.gif)

## Overview

### User flow

As a user, they can explore posts by:

- Scroll to the bottom of the page, then
- Loading for fetching function to be finished, then
- New posts are appended below the last seen post

### File tree:
![File tree](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3g3jpweszbqo01gf4vpm.png)
  

This is how the project file tree looks like:

- The main `App.jsx` and `App.css` are in the root folder
- The smaller components in `components/` folder
- Components' css in `css/` folder
- Anything related to firebase is inside `firebase/`folder

## Enable trigger through styling

There are multiple ways to trigger an action when the user scrolls. In this example, we implement the scroll listener in the most outside component. We can simply use `onScroll`:

```jsx
//App.jsx
//..
<div className="app__post_view"
     onScroll={checkBottom}>
	<h1>Post Pagination Example</h1>
	<div className="app__post_wrapper" >
	// all of the posts here
	</div>
	// bottom part
</div>
```

We call `checkBottom` function if the user scrolls.

We implement the event listener inside a `div` component, not the whole `window` component. A little adjustment to the component's styling is needed. Here's how we style the component:

```css
/* App.css */
/* ... */
.app__post_view {
  /* other important styles */
	overflow-y: scroll;
	height: 100vh;
}
/* ... */
```

We need to limit the height of the component to 100% of the user's viewport height. Any vertical overflow is scrollable. This way, we implement the scrolling mechanism in the component, not to the `window`. Here's the visualization:

![browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u8bdljh2t9wqn1hgortw.png)
 

As what I mentioned before, there are so many ways to trigger action when the user scrolls. In the left figure, we simply add the listener in the browser window. But in this example, we add the scroll listener in the component (right side figure).

## Post fetching

### State hooks

```jsx
// App.jsx
// ..
const [posts, setPosts] = useState([])
const [docs, setDocs] = useState([])
const [fetching, setFetching] = useState(false);
const [morePost, setMorePost] = useState(true)

```

There are 4 state hooks used in this example:

- `posts` state stores all of the fetched posts data
- `docs` stores all of the fetched posts Firebase documents (we actually can replace `posts` with this one, this hooks was later added in the project)
- `fetching` tells whether our app is still waiting for downloading the additional posts or not
- `morePost` is `true` if there is any post that the user hasn't seen. Otherwise, all of the posts in the database have been browsed by the user.

### Bottom check function `checkBottom`

Now, let's check into `checkBottom` function triggered by user's scroll.

```jsx
// App.jsx
// ...
const checkBottom = (e) => {
    const bottom = (
      (e.target.scrollHeight
        - e.target.scrollTop 
        === e.target.clientHeight) &
      (fetching === false) &
      (morePost === true));
      if (bottom) { 
        fetchData()
      }
  }
//..
```

This function calls `fetchData` function if the user hit the bottom end of the component. Moreover, it only calls if the app not in the middle of `fetching`  process and there is additional posts in the database through `morePost` state.

### Fetch posts `fetchData`

```jsx
// App.jsx
import { db } from "./firebase/firebase";
// ..
const fetchData = () => {
    if (fetching === true) return;
    setFetching(true);
    let query = db
    .collection('posts')
    .orderBy('timestamp','desc')

		//.. this block enables pagination
		if (posts.length !== 0) {
      const lastVisible = docs[docs.length-1];
      query = query
      .startAfter(lastVisible);
    }

    query.limit(5)
    .get().then(snapshot=>{
	if (snapshot.docs.length === 0) setMorePost(false);
        setDocs([...docs, ...snapshot.docs])
        setPosts([...posts, ...snapshot.docs.map(doc=> (
          {id: doc.id,
            post: doc.data()}
            ))])
          }).then(
	       setFetching(false)
	   );
	  }
```

We extend the existing fetch function from [the first series](https://dev.to/arnoldschan/ig-post-upload-via-firebase-storage-and-firestore-24on-temp-slug-8672326?preview=db80d34dcaa177bb9cf716f59f97472ac6ca5ba8e3432cecf635ff3df5834510d66bf686d1feaa27fe461ae83ac73484f3424139d4d9f38cc50e7f4a) with pagination capability. First, we should check `fetching` state is in `false` to avoid multiple fetches.

In the first fetch, we'll skip the middle `if` statement. We simply fetch the posts data from `db` (Firestore object) ordered by timestamp and limit by 5 posts in each fetch. After we got the data, we update `docs` and `posts` states then sequentially switch the `fetching` state to `false`. 

In the second fetch and after, this function considers the `if` statement in the middle. Here, we update the `query` object with `startAfter` attribute, telling `query` what was the latest fetched data in `lastVisible`. The rest of the function is exactly the same as the first fetch.

## Conclusion

We can easily mimic infinite scroll like we always enjoy from many social media platforms. In this example, we apply it through scroll listener and simple styling in the component.

We also need to paginate in every API call to get additional posts. Do you have any better alternative in implementing infinite scroll? Drop your thoughts below!