Sometimes, you need to render a component but you can't be sure it will be visible to your users. They could be somewhere else on the page, especially if it's a long page. But what if it's an important element, and you need to make sure they see it?
An example of this use case would be a <Alert />
component.
You might need to show an error, a warning, or a success message.
For example, after a user submits a form, you might want to show them a success alert to tell them the submission was successful. Or an error if it wasn't successful!
But what if the user has submitted a long form, and the <Alert />
component is right back up at the top of the page?
They won't see it! 🤷♀️
What a nightmare! They don't know if the form succeeded or failed.
So that your users don't need to scroll around the page looking for answers, we need to get React to scroll to the element so the user can see what’s gone wrong (or right!).
You could do this after the form is submitted, like:
submitForm().then((response) => {
setAlert("This is the alert"); // shows the alert
scrollToAlert(); // scrolls to the alert
});
But instead of adding this kind of logic to every form or place you might need to show an alert (or whatever element you need to scroll to), you can do it in the component, with useEffect
and useRef
.
useRef()
to give your element a ref
Since we need to tell the browser where to go, we need to have access to the DOM node for our element in React.
useRef()
is perfect for this!
Let's start by giving our element a ref
so we have something to scroll to.
import React from 'react';
// example alert component
function Alert({alert, ...props}) {
// use the hook
const alertRef = React.useRef(null);
return (
<div ref={alertRef}>{alert}</div>
);
}
We created the ref object const alertRef = React.useRef(null);
(I've called it alertRef
but you can name it whatever you like).
And then we passed the ref object in the ref
attribute on the element we want to scroll to <div ref={alertRef}>
.
Now we are ready to scroll!
useEffect()
to scroll on render
We have our ref
and component all set up - all we need to do now is get the browser to scroll!
We're going to useEffect()
since we are connecting with a system outside of React - the browser.
And here's how that will look:
React.useEffect(() => {
if (alertRef.current) {
alertRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, []); // no dependencies (run once)
What useEffect()
function does, is check that the ref has been attached to an element if (ref.current)
and then if it has, scroll it into view with the very helpfully named scrollIntoView
element method.
Here's the updated code for our Alert
component now.
import React from 'react';
// example alert component
function Alert({alert, ...props}) {
// use the hook
const alertRef = React.useRef(null);
// scroll into view on render
React.useEffect(() => {
if (alertRef.current) {
alertRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, []); // no dependencies (run once)
return (
<div ref={alertRef}>{alert}</div>
);
}
🚨 This code works if your component is rendered by the parent, e.g. { alert && <Alert />}
- if not, keep reading!
If your component is rendered by a parent component - like with a conditional &&
statement (e.g. { alert && <Alert />}
) - the above code will work. When your component renders, the browser will scroll it into view.
But if your component is in charge of rendering itself - for example, the parent renders <Alert alert={alert} />
and the Alert
component renders like this:
if (!alert) {
return null;
}
We need to tweak the code slightly:
import React from 'react';
// example alert component
function Alert({alert, ...props}) {
// use the hook
const alertRef = React.useRef(null);
// scroll into view on render
React.useEffect(() => {
if (alert && alertRef.current) {
alertRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [alert]); // run if there's an alert
if (!alert) {
return null;
}
return (
<div ref={alertRef}>{alert}</div>
);
}
We've added the alert
prop that triggers the component to render to the dependency array on our useEffect()
. Now whenever the alert
prop is a truthy value and the component renders, our useEffect()
will run and scroll to the element.
The custom useScrollOnRender
hook
If you only need to scroll to one component, like our Alert
component example, you don't need a reusable hook (since you don't need to reuse the code anywhere else!).
You can just pop the code from the examples above in your component and you are good to go.
But if you find you have a few components you want to "scroll into view" on render, let's build a reusable hook so you don't need to keep adding this useEffect
in multiple places.
📄 hooks/useScrollOnRender.js
import React from 'react';
// hook to scroll to an element on render
function useScrollOnRender() {
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current) {
ref.current.scrollIntoView({ behavior: 'smooth' });
}
}, []);
return ref;
};
export default useScrollOnRender;
The hook creates a ref with useRef
and then has the useEffect
we created earlier to scroll the element attached to the ref.
But there is no element attached to the ref (yet)! And that's what makes it reusable.
The hook creates the ref object, but instead of using it, it returns the ref so we can use it in our components.
Here's how you can use the hook:
import React from 'react';
import useScrollOnRender from '../hooks/useScrollOnRender';
// example alert component
function Alert({alert, ...props}) {
// use the hook
let alertRef = useScrollOnRender();
return (
<div ref={alertRef}>{alert}</div>
);
}
Now whenever an alert pops up, the browser obediently will scroll to it.
Here it is in action:
<div id="app"></div>
// hook to scroll to an element on render function useScrollOnRender() { const ref = React.useRef(null); React.useEffect(() => { if (ref.current) { ref.current.scrollIntoView({ behavior: 'smooth' }); } }, []); return ref; }; // example alert component function Alert() { let alertRef = useScrollOnRender(); return ( <div ref={alertRef} className="border border-red-500 bg-red-50 text-red-500 font-bold p-5 my-2">I am an alert! I must be read!</div> ); }; // app component function App() { const [alert, setAlert] = React.useState(false); return ( <div className="p-10"> <h1 className="font-semibold text-3xl py-2">Scroll On Render</h1> { alert && <Alert />} <p>Scroll to the bottom and click the button!</p> <div className="h-96 bg-red-500"></div> <div className="h-96 bg-blue-500"></div> <div className="py-2 h-96 bg-green-500"></div> <button onClick={() => setAlert(!alert)} className="rounded my-5 bg-gray-100 font-bold py-3 px-4">Toggle Alert</button> </div> ) }; // ======================================== // render the react app ReactDOM.render( <App />, document.getElementById('app') );