Table of contents
How to restore scroll position in React Router 6
Web Development
Web Development
When developing a Single Page Application (SPA) with React and React Router V6, I encountered a scroll restoration issue. When I go to the new page, the scroll position persists instead of being on the top
Although React Router V6 offers a ScrollRestoration component, you can try it first!
Because my application requirements are quite complicated so I decided to create a custom ScrollRestoration.
If you use V4 or V5, you can check this package: react-scroll-restoration
Problem
React Router behavior
The scroll position persists between pages when navigating using Link/navigate/browser back button... in the app.
Desired behavior
The scroll position persists between pages when navigating using Link/navigate/browser back button... in the app.
Desired behavior
- When I go to the new page, the scroll position should be on the top
- When users visit page A, scroll down, and click a link that takes them to page B. If they navigate back to page A using the browser's back button, the scroll position should be restored. If they navigate back to A using a link to A on page B, the scroll position should be at the top of the page.
Solution
To address this problem, we need a mechanism to store and restore the scroll position for each page in our application.
Instead of listening to popstate event (more details), I use key in the location object provided by React Router to detect if the browser back button clicked:
Instead of listening to popstate event (more details), I use key in the location object provided by React Router to detect if the browser back button clicked:
- When users click a link or we call navigate function, the key changes
- When users click a browser back button, the key does not change
Custom ScrollRestoration
// language: javascript import React, { useEffect, useState } from 'react' import { useBlocker, useLocation } from 'react-router-dom' type ScrollStates = { [key: string]: number } type PageKeys = { [key: string]: string } const ScrollRestoration = () => { const location = useLocation() const scrollableParent = document.querySelector('main') const [pageKeys, setPageKeys] = useState<PageKeys>({}) const [scrollStates, setScrollStates] = useState<ScrollStates>({}) useEffect(() => { if (!scrollableParent) return const pathName = location.pathname const clickedBackButton = pageKeys[pathName] === location.key if (clickedBackButton) { scrollableParent.scrollTo(0, scrollStates[pathName] || 0) } else { scrollableParent.scrollTo(0, 0) } const newPageKeys = { ...pageKeys, [pathName]: location.key } setPageKeys(newPageKeys) }, [location]) useBlocker(() => { if (scrollableParent) { const newScrollState = { ...scrollStates, [location.pathname]: scrollableParent.scrollTop } setScrollStates(newScrollState) } return false }) return <></> } export default ScrollRestoration
Code Explanation
- In most cases, scrollableParent will be the window object. However, in my project, I specifically target the <main> tag for scrolling purposes.
- pageKeys is used to store the key associated with each pathname, while scrollStates tracks the scroll position for each pathname. Because I add the ScrollRestoration component in the React Router layout, it doesn't remount during navigation. If you place elsewhere, where pageKeys and scrollStates might be lost during navigation, consider using global state or sessionStorage instead of React state.
- clickedBackButton = pageKeys[pathName] === location.key determines whether the back button was clicked by comparing the current pathname's key with the location key (explained above)
- useBlocker is used to store the scroll position of the page before navigation (more details). It always returns false so as not to block navigation.
Happy coding!
Created at
2024-03-21 19:31:45 +0700
Related blogs
[PART 1] Unlocking JavaScript Interview: Promises and Timers
JavaScript interviews often include questions about asynchronous programming, as it's a fundamental concept in modern web development. One common chal...
Web Development
Web Development
2024-06-23 16:51:19 +0700
How Google achieves seamless SSO across multiple domains like Gmail and Youtube?
Hey there! Ever wondered how you can log into Gmail and then magically find yourself logged into YouTube, Google Drive, and all other Google services ...
Web Development
Web Development
2024-09-24 22:52:06 +0700