Lesson

React scroll-to-top button

Add a button to allow users to scroll to the top of a page when clicked

Introduction

A lot of sites, including this one for some time, don't have an easy way for their users to get back to the top of the page. Sure, the user could manually scroll, but this can be frustrating, especially on really long pages.

That's why a button, usually postioned in the bottom-right of the page, that allows users to get back to the top of the page in a single click is useful. In this lesson I'll walk you through each step on how to create a scroll-to-top button component in React.

Watch the lesson

Project repo

The code for this project is open source and available on GitHub. I use Gumroad for those that want to be generous and donate, but no purchase is required. 😊

Grab the code

Next.js and Tailwind CSS starter

In this project I'm using Next.js and Tailwind CSS, but at the end of the day all we're doing is creating a React component. Feel free to use Next.js, Gatsby, Create React App, or whatever library or starter you want. The core concepts are the same as long as you're building with React.

Styling your component is also up to your project preferences. I've been enjoying Tailwind CSS lately and use it in this lesson, but feel free to use any CSS solution you want. Check out the Tailwind installation guide if you're interested in using Tailwind for your project.

ScrollToTop component

Start by creating a new component named ScrollToTop.js and write the following code:

import { useEffect, useState } from 'react'
import { BiArrowFromBottom } from 'react-icons/bi'

import { classNames } from '../../utils'

export const ScrollToTop = () => {
  const [isVisible, setIsVisible] = useState(false)

  const toggleVisibility = () => {
    if (window.pageYOffset > 300) {
      setIsVisible(true)
    } else {
      setIsVisible(false)
    }
  }

  const scrollToTop = () => {
    window.scrollTo({
      top: 0,
      behavior: 'smooth',
    })
  }

  useEffect(() => {
    window.addEventListener('scroll', toggleVisibility)

    return () => {
      window.removeEventListener('scroll', toggleVisibility)
    }
  }, [])

  return (
    <div className="fixed bottom-2 right-2">
      <button
        type="button"
        onClick={scrollToTop}
        className={classNames(
          isVisible ? 'opacity-100' : 'opacity-0',
          'inline-flex items-center p-3 rounded-full shadow-sm text-white bg-pink-600 transition-opacity hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500',
        )}
      >
        <BiArrowFromBottom className="h-6 w-6" aria-hidden="true" />
      </button>
    </div>
  )
}

React useEffect and useState hooks

You start by importing useEffect and useState hooks from React. The useEffect hook will be used to handle side effects in the component and the useState hook will be used to create a isVisible state value and setIsVisible state function. You can learn more about these hooks from the useState guide and useEffect guide.

React icons

The next import is from React icons, an optional package that includes a variety of free icon components. The one we're using in this component comes from the Box Icons set.

className utility function

The classNames function is a simple utility funtion that we'll use to join Tailwind CSS classnames:

export const classNames = (...classes) => {
  return classes.filter(Boolean).join(' ')
}

The first thing this function does is to check that the item passed in from the array is truthy. Michael Uloth wrote a great post on how the filter(Boolean) trick works in JavaScript in case you want to learn more.

Next, it uses join(' ') to combine all the values in the array with a space so the className is correctly formatted.

useState hook

Inside the actual component you start by creating a new state value and state function using the useState React hook. The state value is isVisible and the function used to update the state value is setIsVisible. You can call these whatever you want, but it's a good idea to stick to a similar naming convention so your state values are easy to read.

toggleVisibility function

The toggleVisibility function uses the window interface to check if the user's vertical scroll position is great than 300. If true the function will set the isVisible state to true. Else, it will set the isVisible state to false.

scrollToTop

The scrollToTop function also uses the window interface, but this time it's using a method called scrollTo. This method takes X and Y coordinates as arguments, or an option arguement where you can pass in position values and a behavior value. By default the browser will automatically jump to the top of the page, but with behavior: 'smooth' the user will experience a smooth scroll transition.

useEffect hook

In the useEffect hook you use the addEventListener method to add an event listener on the window interface that listens for scroll events. If a scroll event is triggered it will fire the toggleVisibility function.

Any time you add an event listener you also need to tell the component to stop listening for the events in the cleanup function. To do that you used the removeEventListener method on the window interface. It's important to add this cleanup function to avoid memory leak issues and errors in React.

Component return

This part of the code is the button HTML and CSS styles. You'll notice that the classNames utility function is being used to combine the return value from a ternary operator with some other general Tailwind CSS classnames.

The ternary operator is being used to add conditional styles to control the button's opacity depending if the isVisible state value is true or false.

Using the button

With the ScrollToTop component completed you can now use it on any page you want. For example, you can add it to index.js page like so:

import { ScrollToTop } from '../components'

export default function Home() {
  return (
    <>
      <ScrollToTop />
      {/* Lots of content */}
    </>
  )
}

Conclusion

It's a nice touch to add a scroll-to-top button to any long page in your React application. It saves your user some time from having to scroll manually, plus it's really simple to implement using the window interface, basic CSS, and a couple React hooks.