Scroll a React component into view
React refs give you access to the underlying DOM element when you need a browser API directly. One common example is scrolling an element into view after a button click.
People got much more comfortable scrolling long pages over time. We are now used to websites where not everything is visible at first glance.
So how do you move someone to content that is currently outside the viewport? The browser already has an API for that: Element.scrollIntoView(). It does what the name says, with a few useful options for changing the behavior.
Scroll to element with plain HTML
Before using it in React, start with the plain HTML version.
Suppose we have an article with a long body:
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>
At the end of the article, we can add a link that points back to the <h1> by id:
html<article>...<a href="#title"> Back to the top </a></article>
When the user clicks the link, the browser jumps back to the title. That is the simplest way to scroll an element into view without JavaScript.
Scroll to element with vanilla JavaScript
To do the same thing with JavaScript, create a button that scrolls back to the top when clicked:
html<article>...<button onclick="document.getElementById('title').scrollIntoView()">Back to the top</button></article>
The button finds the heading by its title id and tells the browser to scroll it into the viewport.
For many cases, that is enough. If you want a smooth animation, pass an options object:
jsconst titleElement = document.getElementById('title')titleElement.scrollIntoView({ behavior: 'smooth' })
With behavior set to smooth, the browser scrolls to the element instead of jumping there instantly.
Scroll to a React element
In React, we can still use Element.scrollIntoView(). We just need access to the underlying HTML element.
First, convert the example to a function component:
jsximport 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 keep the id, but a ref gives React direct access to the element. Use useRef for that. You can read more about useRef() in the React documentation.
jsximport 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>)}
Next, handle the button click with an onClick event handler. You can read more about event handling in the React documentation.
jsximport 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>)}
Inside the event handler, the title element is available through the ref. Then we can call scrollIntoView() just like before.
jsxconst titleRef = useRef()function handleBackClick() {titleRef.current.scrollIntoView({ behavior: 'smooth' })}
useRef() gives the component access to the underlying HTML element, including the DOM APIs on that element.
Scroll to a React component
The same idea works when the element lives inside another React component. Forward the ref to the component's root element, and the parent can still call the DOM API.
jsximport 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} />}
The forwardRef() call lets another component access the <h1> inside Article by ref. You can read more about forwardRef() in the React documentation.
Bonus: scroll to the first error in a Formik form
For a more realistic use case, imagine a large React form using Formik for submission and validation. Here is a small newsletter signup form:
jsximport 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 the user submits the empty form, it shows an error for the email field. In this tiny form, that is easy to spot. In a larger form, it helps to scroll to the first field with an error.
Add a small helper component inside the form:
jsximport 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 <ErrorFocus> to the Formik form. When validation fails, the user is scrolled to the first input with an error.
jsximport 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
Refs are useful when React code needs to call a browser API directly. This post used Element.scrollIntoView(), but the same pattern applies to other element methods too.
For example, elements can be animated through JavaScript with Element.animate(). The MDN documentation covers that API in more detail.