I struggled with getting scroll restoration to work for a long time.
Imagine you're on a search screen, scrolled far down. You click an item, which takes you to a new screen. Then you go back, but you're back at the top of the search screen. It's a jarring UX.
Once I finally landed on a solution for Next.js, it didn't work with React Native Web. Why? Because react-native-web
uses a ScrollView
component, rather than window scrolling. This means that the common window.scrollTo
solutions out there won't work.
The purpose of this gist is to make a drop-in solution that restores your scroll position whenever you use a FlatList
or ScrollView
and your users go back (i.e. they pop
).
The files are all included in this gist. There's really only one you need (react-native-next-scroll-restore.ts
); the rest are illustrative to show you what an example would look like.
In my case, I have some screens that use window scrolling, others that don't. This gist adds support for both. Apparently Next.js 10.x+ is going to have scroll restoration for window scrolling, so maybe one of them won't be needed. We shall see.
Create the file that exports ReactNativeNextJsScrollRestore
.
Call ReactNativeNextJsScrollRestore.initialize()
in your pages/_app.tsx
file. This sets up a next/router
tracker that checks if your user has gone "back". If they visited a page but did not go back, it deletes previous scroll history for that page, so that it doesn't restore scroll.
// pages/_app.js
import ReactNativeNextJsScrollRestore from '../react-native-next-scroll-restore'
import { useEffect } from 'react'
function MyApp({ Component, pageProps }) {
useEffect(() => {
const unsubscribe = ReactNativeNextJsScrollRestore.initialize()
return () => {
unsubscribe()
}
}, [])
return <Component {...pageProps} />
}
Now you need to hook up your React Native Scrollable.
// components/search-list
import React from 'react'
import ReactNativeNextJsScrollRestore from '../react-native-next-scroll-restore'
import { ScrollView, View } from 'react-native'
const { useScroller } = ReactNativeNextJsScrollRestore
export default function SearchList() {
const scrollRef = React.useRef<ScrollView>(null)
const { onScroll } = useScroller(scrollRef, {
// optional, in ms
scrollDelay: 500
})
// IMPORTANT: ScrollView parent must have a fixed height or flex: 1
// See: https://github.com/necolas/react-native-web/issues/956
return (
<View style={{ flex: 1 }}>
<ScrollView ref={scrollRef} onScroll={onScroll}>
<YourScrollableContentHere />
</ScrollView>
</View>
)
}
useScroller
does two things here:
- It takes your scrollables
ref
as its first argument. This is used toscrollTo
when the page mounts (if the user just went back, meaning there is potentially a scroll position to restore.)
- You can also pass a
scrollDelay
option in the second argument. This lets you specify how long the function should sleep before it triggers your scroll. My default is350
. If your page takes a longggg time to render, maybe go longer. If you make this number too low, it might try to restore your scroll before your content has even rendered, which would render it useless. - If you want to add
animated: boolean
as an option here, you could do that too, but you'll have to editreact-native-next-scroll-restore.ts
to pass the prop down. If you know you're scrolling for more than like 2000px, I might recommend disabling animation, since it can be kind of choppy for such a long distance.
- Create a simple
onScroll
function that you pass to yourScrollView
. This updates thescrollViewMemories
variable with the latest scroll position. That way, if we visit this page again in the future, we know where the user left off.
If you've made it to the end of this random GitHub Gist's readme, congrats. You've run into quite the niche problem.
It took me a long time to get to a solution for this. Hopefully, it only takes you a few minutes.
If this helped you out, let me know on Twitter (fernandotherojo) so it will feel worth it.
FWIW, you can also opt out of using a scroll view on Web altogether, and then Next.js will restore scroll for you. This is outlined in the solito docs: https://solito.dev/recipes/scroll-view