Full Stack Instagram: Infinite Scroll

arnoldschan

Arnold Samuel Chan

Posted on May 15, 2021

Full Stack Instagram: Infinite Scroll

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

paginate

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

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:

//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>
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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

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

Enter fullscreen mode Exit fullscreen mode

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.

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

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

// 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)
       );
      }
Enter fullscreen mode Exit fullscreen mode

We extend the existing fetch function from the first series 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!

💖 💪 🙅 🚩
arnoldschan
Arnold Samuel Chan

Posted on May 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related