loading...
Cover image for How to Create a Custom usePageBottom() React Hook

How to Create a Custom usePageBottom() React Hook

codeartistryio profile image Reed Barger Originally published at reedbarger.com on ・3 min read

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() {}

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(() => {}, []);
}

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;
  }, []);
}

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;
}

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;
}

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;
}

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]);
}

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

Want To Become a JS Master? Join the 2020 JS Bootcamp

Join the JS Bootcamp Course

Follow + Say Hi! 🎨 TwitterInstagramreedbarger.comcodeartistry.io

Posted on by:

codeartistryio profile

Reed Barger

@codeartistryio

Sharing artful coding skills that fuel the life you want to live @ CodeArtistry.io 🎨

Discussion

markdown guide
 

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!

 

Clear and concise. Love the simplicity

 

Beautiful, thnks for sharing!