Scroll a React component into view
- Published
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.
html
<article><h1 id="title">An interesting article for Latin readers</h1><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sitamet luctus neque. Etiam eu quam lacinia, placerat sem ut, interdumrisus. 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 nequenon elit laoreet, faucibus tempor ante gravida.</p><p>Duis in ante turpis. Phasellus dignissim tellus et nunc laciniaelementum. Sed venenatis tincidunt justo. Praesent sed purus facilisis,porttitor ligula in, mattis velit. Curabitur non pellentesque nunc. Duiselit urna, bibendum et purus nec, maximus imperdiet mauris. Craseuismod, leo id vehicula vulputate, nibh massa tincidunt justo, sit ametfringilla quam orci pellentesque enim.</p><p>...</p></article>
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.
html
<article>...<a href="#title"> Back to the top </a></article>
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.
html
<article>...<button onclick="document.getElementById('title').scrollIntoView()">Back to the top</button></article>
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.
js
const titleElement document.getElementById('title')titleElement.scrollIntoView({ behavior: 'smooth' })
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.
jsx
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>)}
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.
jsx
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>)}
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.
jsx
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>)}
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.
jsx
const titleRef = useRef()function handleBackClick() {titleRef.current.scrollIntoView({ behavior: 'smooth' })}
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.
jsx
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} />}
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.
jsx
import React from 'react'import { Formik } from 'formik'const SignupForm = () => {return (<FormikinitialValues={{ 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><inputid="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>)}
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.
jsx
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}
Now add this <ErrorFocus>
component to our Formik form and the user is automatically scrolled to the first input that has a validation error.
jsx
import React from 'react'import { Formik } from 'formik'import ErrorFocus from './error-focus'const SignupForm = () => {return (<FormikinitialValues={{ 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><inputid="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>)}
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.