DEV Community

Cover image for How to Create a Custom usePageBottom() React Hook
Reed Barger
Reed Barger

Posted on • Originally published at reedbarger.com on

How to Create a Custom usePageBottom() React Hook

In React apps, sometimes it is important to know when your user has scrolled to the bottom of a page.

In apps where you have an infinite scroll, such as Instagram for example, once the user hits the bottom of the page, you need to fetch more posts.

Infinite scroll in Instagram

Let’s take a look at how to create a usePageBottom hook ourselves for similar use cases like creating an infinite scroll.

We’ll begin by making a separate file, usePageBottom.js, in our utils folder and add a function (hook) with the same name:

// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {}

Enter fullscreen mode Exit fullscreen mode

Next, we’ll need to calculate when our user hits the bottom of the page. We can determine this with information from the window. In order to access this, we’re going to need to make sure our component that the hook is called within is mounted, so we’ll use the useEffect hook with an empty dependencies array.

// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  React.useEffect(() => {}, []);
}

Enter fullscreen mode Exit fullscreen mode

The user will have scrolled to the bottom of the page when the window’s innerHeight value plus the document’s scrollTop value is equal to the offsetHeight. If those two values are equal, the result will be true, and the user has scrolled to the bottom of the page:

// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  React.useEffect(() => {
    window.innerHeight + document.documentElement.scrollTop === 
    document.documentElement.offsetHeight;
  }, []);
}

Enter fullscreen mode Exit fullscreen mode

We’ll store the result of this expression in a variable, isBottom and we’ll update a state variable called bottom, which we’ll ultimately return from our hook.

// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  const [bottom, setBottom] = React.useState(false);

  React.useEffect(() => {
    const isBottom =
      window.innerHeight + document.documentElement.scrollTop ===
      document.documentElement.offsetHeight;
    setBottom(isButton);
  }, []);

  return bottom;
}

Enter fullscreen mode Exit fullscreen mode

Our code as is, however, won’t work. Why not?

The issue lies in the fact that we need to calculate isBottom whenever the user is scrolling. As a result, we need to listen for a scroll event with window.addEventListener. We can reevaluate this expression by creating a local function to be called whenever the user scroll, called handleScroll.

// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  const [bottom, setBottom] = React.useState(false);

  React.useEffect(() => {
    function handleScroll() {
      const isBottom =
        window.innerHeight + document.documentElement.scrollTop 
        === document.documentElement.offsetHeight;
      setBottom(isButton);
    }
    window.addEventListener("scroll", handleScroll);
  }, []);

  return bottom;
}

Enter fullscreen mode Exit fullscreen mode

Finally, since we have an event listener that is updating state, we need to handle the event that our user navigates away from the page and our component is removed. We need to remove the scroll event listener that we added, so we don’t attempt to update a state variable that no longer exists.

We can do this by returning a function from useEffect along with window.removeEventListener, where we pass a reference to the same handleScroll function. And we’re done.

// utils/usePageBottom.js
import React from "react";

export default function usePageBottom() {
  const [bottom, setBottom] = React.useState(false);

  React.useEffect(() => {
    function handleScroll() {
      const isBottom =
        window.innerHeight + document.documentElement.scrollTop 
        === document.documentElement.offsetHeight;
      setBottom(isButton);
    }
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return bottom;
}

Enter fullscreen mode Exit fullscreen mode

Now we can simply call this code in any function where we want to know whether we’ve hit the bottom of the page or not. Here is one example of how I’ve used it in a Feed component that uses an infinite scroll:

// components/Feed.js
import React from "react";
import usePageBottom from "../utils/usePageBottom";

function Feed() {
  const isPageBottom = usePageBottom();

  React.useEffect(() => {
    // if no data or user hasn't scroll to the bottom, don't get more data
    if (!isPageBottom || !data) return;
    // otherwise, get more posts
    const lastTimestamp = data.posts[data.posts.length - 1].created_at;
    const variables = { limit: 20, feedIds, lastTimestamp };
    fetchMore({
      variables,
      updateQuery: handleUpdateQuery,
    });
  }, [isPageBottom, data, fetchMore, handleUpdateQuery, feedIds]);
}

Enter fullscreen mode Exit fullscreen mode

Feel free to use this hook yourself in your own client-rendered React apps!

Become a React Developer in 5 Weeks

React is hard. You shouldn't have to figure it out yourself.

I've put everything I know about React into a single course, to help you reach your goals in record time:

Introducing: The React Bootcamp

It’s the one course I wish I had when I started learning React.

Click below to try the React Bootcamp for yourself:

Click to join the React Bootcamp
Click to get started

Top comments (3)

Collapse
 
vunderkind profile image
mogwai

Hey Reed. This is bloody brilliant! In the first few lines of this, I already found myself looking up scrollTop in the MDN. Really intriguing.

(there is a useState typograpical error, where you say setIsBottom(isButton), and I suspect you intended to write setIsBottom(isBottom)).

Excellent stuff!

Collapse
 
monfernape profile image
Usman Khalil

Clear and concise. Love the simplicity

Collapse
 
leired7 profile image
Leired7

Beautiful, thnks for sharing!