Last active
August 2, 2024 06:25
-
-
Save GusRuss89/df05ea25310043fc38a5e2ba3cb0c016 to your computer and use it in GitHub Desktop.
Nextjs - keep state and scroll position between page transitions. Described in detail here - https://medium.com/@angus.russell89/next-js-keep-page-components-mounted-between-page-transitions-and-maintain-scroll-position-205b34539a26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef, useEffect, memo } from 'react' | |
import { useRouter } from 'next/router' | |
const ROUTES_TO_RETAIN = ['/dashboard', '/top', '/recent', 'my-posts'] | |
const App = ({ Component, pageProps }) => { | |
const router = useRouter() | |
const retainedComponents = useRef({}) | |
const isRetainableRoute = ROUTES_TO_RETAIN.includes(router.asPath) | |
// Add Component to retainedComponents if we haven't got it already | |
if (isRetainableRoute && !retainedComponents.current[router.asPath]) { | |
const MemoComponent = memo(Component) | |
retainedComponents.current[router.asPath] = { | |
component: <MemoComponent {...pageProps} />, | |
scrollPos: 0 | |
} | |
} | |
// Save the scroll position of current page before leaving | |
const handleRouteChangeStart = url => { | |
if (isRetainableRoute) { | |
retainedComponents.current[router.asPath].scrollPos = window.scrollY | |
} | |
} | |
// Save scroll position - requires an up-to-date router.asPath | |
useEffect(() => { | |
router.events.on('routeChangeStart', handleRouteChangeStart) | |
return () => { | |
router.events.off('routeChangeStart', handleRouteChangeStart) | |
} | |
}, [router.asPath]) | |
// Scroll to the saved position when we load a retained component | |
useEffect(() => { | |
if (isRetainableRoute) { | |
window.scrollTo(0, retainedComponents.current[router.asPath].scrollPos) | |
} | |
}, [Component, pageProps]) | |
return ( | |
<div> | |
<div style={{ display: isRetainableRoute ? 'block' : 'none' }}> | |
{Object.entries(retainedComponents.current).map(([path, c]) => ( | |
<div | |
key={path} | |
style={{ display: router.asPath === path ? 'block' : 'none' }} | |
> | |
{c.component} | |
</div> | |
))} | |
</div> | |
{!isRetainableRoute && <Component {...pageProps} />} | |
</div> | |
) | |
} | |
export default App |
Any thoughts on how to achieve this without window scrolling? For instance, if I’m using a ScrollView
from React Native Web (which is just a div with overflow-y: scroll
) and I want to restore its scroll position. I suppose I could try to track it globally somehow, but it’s a bit cumbersome to solve.
@nandorojo I'm not familiar with ScrollView
but surely it has some way to get and set the scroll position? If so it should be exactly the same but with those methods instead of window.scrollY
and window.scrollTo()
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This custom app.js file maintains the page component state between specified components, including scroll position.
This is the solution currently in use on NightCafe Creator.
There are a couple of issues with this:
getInitialProps
function before transitioning the routeI think the only way to solve issue 1 is to keep the state in redux (or another global state manager) and check if the state exists before running any async tasks in
getInitialProps
.A better way to manage scroll position would be to put each page inside its own absolutely positioned container that can keep its own scroll position. The container div could be positioned off-screen rather than hidden.
This is described in more detail in a blog post on Medium