Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React: Fix a flash of scrolling-to-top on page changes by using useLayoutEffect #1803

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

oscarnewman
Copy link

@oscarnewman oscarnewman commented Feb 20, 2024

This is my (attempted) fix for #1802. I outlined the issue in detail there but tl;dr:

  • Inertia/React creates a race condition when changing pages, where resetting the scroll position to (0,0) often happens an instant before the page component itself changes to the next page
  • This creates a flash of unexpected content when you'll scrolled down a page and click a <Link>.

A huge caveat to all of this is that I'm brand new to Inertia as a user, let alone looking at this codebase, so please take anything I suggest with a grain of salt.

Based on how Remix/React-router handle this successfully with a useLayoutEffect, I've slightly adapted a few things:

  1. each framework package (react/vue/etc) can now specific in it's router.init whether it will handle resetting scroll position itself (vs letting the router core decide when to do it)
  2. the swapComponent callback within router.init now passes a new argument, preserveScroll, similar to how it passes preserveState. This and (1) mean React has the information needed to manage scroll resetting in it's own render lifecycle
  3. router.resetScrollPositions() is now public
  4. Existing calls to router.resetScrollPositions() adjacent to the swapComponent call have been replaced with router.resetScrollPositionsIfNotHandledExternally() -- which just does nothing if the handleScrollResetsExternally flag is set by the framework package. Other calls to router.resetScrollPositions() weren't changed as they don't interact with the swapComponent function and don't seem to suffer the same issue

I've also updated the React playground with a sticky header so that the fix can be validated there as well.

Testing so far:

  1. Tested the fix in the playground ✅
  2. Linked the rebuild inertia into my own app, and the more aggressive flashes are gone ✅
  3. Ensured that preserveScroll still works on Link and Router calls ✅

@@ -484,7 +496,7 @@ export class Router {
Promise.resolve(this.resolveComponent(page.component)).then((component) => {
if (visitId === this.visitId) {
this.page = page
this.swapComponent({ component, page, preserveState: false }).then(() => {
this.swapComponent({ component, page, preserveState: false, preserveScroll: true }).then(() => {
this.restoreScrollPositions()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, restoreScrollPositions() doesn't suffer from this same issue in my testing. I'm not entirely sure why, to be honest.

setCurrent((current) => ({
component,
page,
key: preserveState ? current.key : Date.now(),
preserveScroll: !!preserveScroll,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both preserveState and preserveScroll are typed as a PreserveStateOption in the router core, although they actually seem to be resolved to booleans where it matters in the usage here?

This is where my knowledge of this codebase starts to feel super minimal -- so I may be way off here

@@ -5,7 +5,7 @@ export default function Layout({ children }) {

return (
<>
<nav className="flex items-center space-x-6 bg-slate-800 px-10 py-6 text-white">
<nav className="sticky top-0 flex items-center space-x-6 bg-slate-800 px-10 py-6 text-white">
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allows for reproduction to be tested -- you need to be able to click a link while in a scrolled-down state

@oscarnewman oscarnewman marked this pull request as ready for review February 20, 2024 04:36
@driesvints driesvints closed this Mar 29, 2024
@driesvints driesvints reopened this Mar 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants