DEV Community

Robin van der Vleuten
Robin van der Vleuten

Posted on • Originally published at robinvdvleuten.nl

Scroll a React component into view

React has an easy way to access DOM APIs of HTML elements through references. We learn how React exposes HTML elements by scrolling an element into view on the click of a button.

People's behavior on websites did not change very much since the early days of the internet. But one thing that did change - since 1994 to be more precise - was that we learned to scroll longer pages of content. We are now used to websites where not all information may be visible at first sight.

But how do we grab a user's attention for something that isn't visible in the current part of the viewport it's currently looking at. We can utilize a very handy browser API for that, called Element.scrollIntoView(). Which does exactly what it says it does with a few nice options to modify its behavior.

Scroll to element with plain HTML

Before diving into the React implementation, let's try out the API on a simple HTML list with vanilla Javascript.

Let's say we have an article with a long text.

<article>
    <h1 id="title">
        An interesting article for Latin readers
    </h1>
    <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sit amet luctus neque. Etiam eu quam lacinia, placerat sem ut, interdum risus. Quisque ut feugiat mauris. Aenean euismod fermentum facilisis. Donec ultricies maximus elit, sit amet convallis urna rhoncus vitae. Aliquam bibendum turpis et felis blandit commodo. Donec egestas neque non elit laoreet, faucibus tempor ante gravida.
    </p>
    <p>
        Duis in ante turpis. Phasellus dignissim tellus et nunc lacinia elementum. Sed venenatis tincidunt justo. Praesent sed purus facilisis, porttitor ligula in, mattis velit. Curabitur non pellentesque nunc. Duis elit urna, bibendum et purus nec, maximus imperdiet mauris. Cras euismod, leo id vehicula vulputate, nibh massa tincidunt justo, sit amet fringilla quam orci pellentesque enim.
    </p>
    <p>
        ...
    </p>
</article>
Enter fullscreen mode Exit fullscreen mode

Whenever a user reached the end of the article, we would like to provide a button to scroll back to the top of the article. This can be achieved by adding a link with the id of the <h1> heading element on the end of the paragraph.

<article>
    ...
    <a href="#title">
        Back to the top
    </a>
</article>
Enter fullscreen mode Exit fullscreen mode

Now when the user clicks the link, the browser will automatically jump back to the title element and the user is back on the top of the article. This is the basic way to scroll an element into view without using any Javascript at all.

Scroll to element with vanilla Javascript

To scroll to the element with Javascript, you can create a button which scrolls back the the top when a user clicks it.

<article>
    ...
    <button onclick="document.getElementById('title').scrollIntoView()">
        Back to the top
    </button>
</article>
Enter fullscreen mode Exit fullscreen mode

By using an event listener on the button, whenever it is invoked we get the heading element by its title identifier and tell it to scroll into the browser's viewport.

For most use cases, this is sufficient. But sometimes you'll would like to have a nice animation while scrolling. Luckily you can pass additional options to the method to do exactly that.

const titleElement document.getElementById('title')
titleElement.scrollIntoView({ behavior: 'smooth' })
Enter fullscreen mode Exit fullscreen mode

By setting the behavior option to smooth, the browser will gently scroll to the element instead of the instant jump.

Scroll to a React element

Now the next step is to figure out how we can apply this smooth scrolling behavior on a React component. We can still use the Element.scrollIntoView() method, but we need to grab the component's underlaying HTML element to access it.

First, let's convert our example to a React functional component.

import React from 'react'

const Article = () => {
  return (
      <article>
            <h1>
                A React article for Latin readers
            </h1>
            <p>
                ...
            </p>
            <p>
                ...
            </p>
            <button>
                Back to the top
            </button>
        </article>
    )
}
Enter fullscreen mode Exit fullscreen mode

We could still give the <h1> element an id attribute. But to do it the React way, we'll give a reference instead with the useRef hook. You can read more about the useRef() hook in the official React documentation.

import React, { useRef } from 'react'

const Article = () => {
  const titleRef = useRef()

  return (
      <article>
            <h1 ref={titleRef}>
                A React article for Latin readers
            </h1>

            // Rest of the article's content...

            <button>
                Back to the top
            </button>
        </article>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now we need to handle the user clicking the button to scroll back to the top. We can use an onClick event handler for that. You can read more about event handling in the official React documentation.

import React, { useRef } from 'react'

const Article = () => {
  const titleRef = useRef()

  function handleBackClick() {
    // Scroll back to the title element...
  }

  return (
      <article>
            <h1 ref={titleRef}>
                A React article for Latin readers
            </h1>

            // Rest of the article's content...

            <button onClick={handleBackClick}>
                Back to the top
            </button>
        </article>
    )
}
Enter fullscreen mode Exit fullscreen mode

Within the event handler, we now have access to the title element through its reference. And we can scroll to the title element like we did in the vanilla Javascript example.

const titleRef = useRef()

function handleBackClick() {
  titleRef.current.scrollIntoView({ behavior: 'smooth' })
}
Enter fullscreen mode Exit fullscreen mode

By using useRef() in a React component, we have an entry to the underlaying HTML element. This gives us full access to all of the powerful DOM APIs.

Scroll to a React component

Now that we have seen how we can scroll to an element by using a reference. We can utilize that same method to scroll to a React component. By forwarding the reference to the root element of the component, we again have access to the HTML element from outside the component.

import React, { forwardRef, useRef } from 'react'

const Article = forwardRef(({ onBackClick }, ref) => {
  return (
      <article>
            <h1 ref={ref}>
                A React article for Latin readers
            </h1>

            // Rest of the article's content...

            <button onClick={onBackClick}>
                Back to the top
            </button>
        </article>
    )
})

// ...

Const AnotherComponent = () => {
    const articleRef = useRef()

    function handleBackClick() {
      articleRef.current.scrollIntoView({ behavior: 'smooth' })
    }

    return <Article ref={articleRef} onBackClick={handleBackClick} />
}
Enter fullscreen mode Exit fullscreen mode

As you may see in the example, we've used the forwardRef() method, to allow other components to access HTML elements within our Article component by reference. You can read more about the forwardRef() method in the official React documentation.

Bonus: scroll to the first error in a Formik form

To apply what we've learned to a real-world use case. Let's imagine we have a large React form using the Formik library to handle submission and validation. For example the following newsletter signup form.

import React from 'react'
import { Formik } from 'formik'

const SignupForm = () => {
  return (
      <Formik
        initialValues={{ email: '' }}
        validate={values => {
        const errors = {}

        if (!values.email) {
              errors.email = 'Required'
            }

            return errors
      }}
      onSubmit={values => {
        // ...
      }}
      >
          {formik => (
            <form onSubmit={formik.handleSubmit}>
              <label htmlFor="email">Email Address</label>
              <input
                id="email"
                name="email"
                type="email"
                onChange={formik.handleChange}
                value={formik.values.email}
              />
              {formik.errors.email ? <div>{formik.errors.email}</div> : null}
              <button type="submit">Submit</button>
            </form>
      )}
    </Formik>
  )
}
Enter fullscreen mode Exit fullscreen mode

When a user tries to submit the form, it will display an error saying that the email field is required. In this small form the user will notice this immediately. But when the form grows larger, it would be nice to scroll the error into the viewport so the user notices the error.

We can do this by creating a small helper component that we add to the form.

import React, { useEffect } from 'react'
import { useFormikContext } from 'formik'

const ErrorFocus = () => {
  // Get the context for the Formik form this component is rendered into.
  const { isSubmitting, isValidating, errors } = useFormikContext()

  useEffect(() => {
    // Get all keys of the error messages.
    const keys = Object.keys(errors)

    // Whenever there are errors and the form is submitting but finished validating.
    if (keys.length > 0 && isSubmitting && !isValidating) {
      // We grab the first input element that error by its name.
      const errorElement = document.querySelector(`input[name="${keys[0]}"]`)

      if (errorElement) {
          // When there is an input, scroll this input into view.
        errorElement.scrollIntoView({ behavior: "smooth" })
      }
    }
  }, [isSubmitting, isValidating, errors])

  // This component does not render anything by itself.
  return null
}
Enter fullscreen mode Exit fullscreen mode

Now add this <ErrorFocus> component to our Formik form and the user is automatically scrolled to the first input that has a validation error.

import React from 'react'
import { Formik } from 'formik'
import ErrorFocus from './error-focus'

const SignupForm = () => {
  return (
      <Formik
        initialValues={{ email: '' }}
        validate={values => {
        const errors = {}

        if (!values.email) {
              errors.email = 'Required'
            }

            return errors
      }}
      onSubmit={values => {
        // ...
      }}
      >
          {formik => (
            <form onSubmit={formik.handleSubmit}>
              <label htmlFor="email">Email Address</label>
              <input
                id="email"
                name="email"
                type="email"
                onChange={formik.handleChange}
                value={formik.values.email}
              />
              {formik.errors.email ? <div>{formik.errors.email}</div> : null}
              <button type="submit">Submit</button>

              {/* The component itself does not render anything, but needs to be within the Formik context */}
              <ErrorFocus />
            </form>
      )}
    </Formik>
  )
}
Enter fullscreen mode Exit fullscreen mode

Closing thoughts

By using useRef() and forwardRef() in your React applications, you will have a lot of powerful DOM APIs at your disposal. In this article we've only focussed on Element.scrollIntoView(), but there are many more cool and handy methods you can use.
Did you know that you can even animate elements through Javascript? The MDN web documentation will tell you more about this Element.animate() method.

Top comments (0)