Scroll Restoration Between Pages in React
In single-page applications (SPAs) like those built with React, users often navigate between different routes, and when they go back to a previous page, they expect to see the content exactly as it was. One common behavior that is important for a seamless user experience is scroll restoration — preserving the scroll position when users navigate between pages.
React Router (especially in combination with React’s state management) doesn’t automatically handle scroll restoration. However, we can implement it manually to ensure the scroll position is maintained across page transitions.
Here’s how you can implement scroll restoration in React:
1. Scroll Restoration with window.scrollTo()
and useEffect
React Router v6+ doesn’t handle scroll restoration by default, but you can implement it yourself using the useEffect
hook and window.scrollTo()
.
Example: Basic Scroll Restoration
- Using
useEffect
for Scroll Restoration
In this example, we will scroll the page back to the top when navigating to a new route, and also save the scroll position before navigating away.
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
const HomePage = () => {
return <div style={{ height: '1500px' }}><h1>Home Page</h1></div>;
};
const AboutPage = () => {
return <div style={{ height: '1500px' }}><h1>About Page</h1></div>;
};
const ScrollToTop = () => {
const location = useLocation();
useEffect(() => {
// Scroll to top when the location changes
window.scrollTo(0, 0);
}, [location]);
return null;
};
const App = () => {
return (
<Router>
<ScrollToTop />
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Router>
);
};
export default App;
Explanation:
window.scrollTo(0, 0)
: Whenever the route changes (i.e., when the location changes), we scroll the page to the top (position0,0
).useEffect
: This hook is triggered every time the location (the current route) changes.useLocation()
: This hook provides access to the current location (i.e., the URL) in the app, allowing us to detect route changes.
2. Preserve Scroll Position on Route Navigation
If you want to preserve the scroll position on route changes (e.g., if a user scrolls down on a page and goes back, you want to return to the same scroll position), you can store the scroll position in state and restore it when the user navigates back.
Example: Preserve and Restore Scroll Position
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
const HomePage = () => {
return <div style={{ height: '1500px' }}><h1>Home Page</h1></div>;
};
const AboutPage = () => {
return <div style={{ height: '1500px' }}><h1>About Page</h1></div>;
};
const ScrollToTop = () => {
const [scrollPositions, setScrollPositions] = useState({});
const location = useLocation();
useEffect(() => {
// Save scroll position on each page
setScrollPositions(prevPositions => ({
...prevPositions,
[location.pathname]: window.scrollY,
}));
}, [location]);
useEffect(() => {
// Restore scroll position if it exists for the current page
if (scrollPositions[location.pathname] !== undefined) {
window.scrollTo(0, scrollPositions[location.pathname]);
} else {
window.scrollTo(0, 0); // Default to top if no scroll position is stored
}
}, [location, scrollPositions]);
return null;
};
const App = () => {
return (
<Router>
<ScrollToTop />
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Router>
);
};
export default App;
Explanation:
- Scroll Positions State:
scrollPositions
stores the scroll positions for each route, indexed by thepathname
. - First
useEffect
: This saves the current scroll position (window.scrollY
) whenever the route changes. - Second
useEffect
: This restores the scroll position for the current route if it’s available in thescrollPositions
state.
3. Scroll Restoration on Back/Forward Navigation
By default, browsers automatically restore the scroll position when the user navigates back to a previously visited page. If you need more control or wish to customize this behavior, you can manage the scroll position manually using the popstate
event.
Example: Advanced Scroll Restoration with popstate
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
const HomePage = () => {
return <div style={{ height: '1500px' }}><h1>Home Page</h1></div>;
};
const AboutPage = () => {
return <div style={{ height: '1500px' }}><h1>About Page</h1></div>;
};
const App = () => {
useEffect(() => {
// Listen for browser back/forward navigation to restore scroll position
const handlePopState = () => {
const scrollY = sessionStorage.getItem(window.location.pathname);
if (scrollY !== null) {
window.scrollTo(0, parseInt(scrollY, 10));
}
};
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, []);
useEffect(() => {
// Save the scroll position whenever the route changes
const saveScrollPosition = () => {
sessionStorage.setItem(window.location.pathname, window.scrollY);
};
window.addEventListener('beforeunload', saveScrollPosition);
return () => window.removeEventListener('beforeunload', saveScrollPosition);
}, []);
return (
<Router>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Router>
);
};
export default App;
Explanation:
- Saving Scroll Position: Using
sessionStorage
, the scroll position is saved whenever the user leaves the page (or navigates away). - Restoring Scroll Position: When the user goes back to a page, the
popstate
event is triggered, and we restore the scroll position fromsessionStorage
. sessionStorage
: Stores data for the duration of the page session. It will be cleared when the page or tab is closed.
4. Using Third-Party Libraries for Scroll Restoration
There are third-party libraries such as react-router-scroll-4
or react-scroll-restoration
that can handle scroll restoration automatically for React Router. These libraries provide an easy way to manage scroll behavior, but they may require additional configuration and setup.