From 8631eda84e4ea3202e52f66f7f911de88ae6f649 Mon Sep 17 00:00:00 2001 From: Dmitry Khorkin <32310771+DimaAmega@users.noreply.github.com> Date: Sun, 25 May 2025 17:40:53 +0400 Subject: [PATCH 1/7] add `scroll with revalidation` test --- .../router/scroll-restoration-test.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/react-router/__tests__/router/scroll-restoration-test.ts b/packages/react-router/__tests__/router/scroll-restoration-test.ts index 0b1dc5838d..edf313c281 100644 --- a/packages/react-router/__tests__/router/scroll-restoration-test.ts +++ b/packages/react-router/__tests__/router/scroll-restoration-test.ts @@ -235,6 +235,46 @@ describe("scroll restoration", () => { expect(t.router.state.restoreScrollPosition).toBe(50); expect(t.router.state.preventScrollReset).toBe(false); }); + + it("does not restore scroll on revalidation", async () => { + let t = setup({ + routes: SCROLL_ROUTES, + initialEntries: ["/"], + }); + + expect(t.router.state.restoreScrollPosition).toBe(null); + expect(t.router.state.preventScrollReset).toBe(false); + + let positions = {}; + + // Simulate scrolling to 100 on / + let activeScrollPosition = 100; + t.router.enableScrollRestoration(positions, () => activeScrollPosition); + + // Revalidate + let R = await t.revalidate(); + await R.loaders.index.resolve("INDEX"); + + expect(t.router.state.restoreScrollPosition).toBe(false); + expect(t.router.state.preventScrollReset).toBe(false); + + // Scroll to 200 + activeScrollPosition = 200; + + // Go to /tasks + let nav1 = await t.navigate("/tasks"); + await nav1.loaders.tasks.resolve("TASKS"); + + expect(t.router.state.restoreScrollPosition).toBe(null); + expect(t.router.state.preventScrollReset).toBe(false); + + // Restore on pop back to / + let nav2 = await t.navigate(-1); + expect(t.router.state.restoreScrollPosition).toBe(null); + await nav2.loaders.index.resolve("INDEX"); + expect(t.router.state.restoreScrollPosition).toBe(200); + expect(t.router.state.preventScrollReset).toBe(false); + }); }); describe("scroll reset", () => { From 211212984940faa52836c7cbb9c132ba5552f8f6 Mon Sep 17 00:00:00 2001 From: Dmitry Khorkin <32310771+DimaAmega@users.noreply.github.com> Date: Sun, 25 May 2025 17:45:10 +0400 Subject: [PATCH 2/7] Update contributors.yml --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index f7a29ecb06..b58fdeb590 100644 --- a/contributors.yml +++ b/contributors.yml @@ -88,6 +88,7 @@ - decadentsavant - developit - dgrijuela +- DimaAmega - DigitalNaut - dmitrytarassov - dokeet From 6549a2b972bc276bfd013500c9aa56030f671eff Mon Sep 17 00:00:00 2001 From: Dmitry Khorkin <32310771+DimaAmega@users.noreply.github.com> Date: Sun, 25 May 2025 17:52:00 +0400 Subject: [PATCH 3/7] fix scroll with revalidation issue --- packages/react-router/lib/router/router.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 81cf0deffb..97188180fb 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -1269,6 +1269,11 @@ export function createRouter(init: RouterInit): Router { blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER)); } + // Don't restore on revalidation + let restoreScrollPosition = + state.revalidation === "idle" && + getSavedScrollPosition(location, newState.matches || state.matches); + // Always respect the user flag. Otherwise don't reset on mutation // submission navigations unless they redirect let preventScrollReset = @@ -1337,10 +1342,7 @@ export function createRouter(init: RouterInit): Router { initialized: true, navigation: IDLE_NAVIGATION, revalidation: "idle", - restoreScrollPosition: getSavedScrollPosition( - location, - newState.matches || state.matches - ), + restoreScrollPosition, preventScrollReset, blockers, }, From e757d9274754537cccba2851975b0e340e2a4293 Mon Sep 17 00:00:00 2001 From: Dmitry Khorkin <32310771+DimaAmega@users.noreply.github.com> Date: Sun, 25 May 2025 17:52:46 +0400 Subject: [PATCH 4/7] fix comment --- packages/react-router/lib/dom/lib.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-router/lib/dom/lib.tsx b/packages/react-router/lib/dom/lib.tsx index 4dfe87c912..89bd04f19d 100644 --- a/packages/react-router/lib/dom/lib.tsx +++ b/packages/react-router/lib/dom/lib.tsx @@ -2077,7 +2077,7 @@ export function useScrollRestoration({ // Restore scrolling when state.restoreScrollPosition changes // eslint-disable-next-line react-hooks/rules-of-hooks React.useLayoutEffect(() => { - // Explicit false means don't do anything (used for submissions) + // Explicit false means don't do anything (used for submissions or revalidations) if (restoreScrollPosition === false) { return; } From 69111ebdbcacc6ecff0f65cd749c6828309eb3f9 Mon Sep 17 00:00:00 2001 From: Dmitry Khorkin <32310771+DimaAmega@users.noreply.github.com> Date: Sun, 25 May 2025 18:31:02 +0400 Subject: [PATCH 5/7] run changeset --- .changeset/kind-paws-try.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/kind-paws-try.md diff --git a/.changeset/kind-paws-try.md b/.changeset/kind-paws-try.md new file mode 100644 index 0000000000..ec0e86d994 --- /dev/null +++ b/.changeset/kind-paws-try.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +fix page twitching when scrolling with revalidation From 43eb65b0dbd616b029ac1ee7d7e497da35a0caae Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 30 Jun 2025 13:49:05 -0400 Subject: [PATCH 6/7] Update router.ts --- packages/react-router/lib/router/router.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 97188180fb..a7f3cfb8db 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -310,7 +310,7 @@ export interface RouterState { /** * Current scroll position we should start at for a new view * - number -> scroll position to restore to - * - false -> do not restore scroll at all (used during submissions) + * - false -> do not restore scroll at all (used during submissions/revalidations) * - null -> don't have a saved position, scroll to hash or top of page */ restoreScrollPosition: number | false | null; @@ -1269,9 +1269,9 @@ export function createRouter(init: RouterInit): Router { blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER)); } - // Don't restore on revalidation - let restoreScrollPosition = - state.revalidation === "idle" && + // Don't restore on router.revalidate() + let restoreScrollPosition = isUninterruptedRevalidation ? + false : getSavedScrollPosition(location, newState.matches || state.matches); // Always respect the user flag. Otherwise don't reset on mutation From d0e6555a5908199619a23bbd79c5df87fed93efc Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 30 Jun 2025 13:49:56 -0400 Subject: [PATCH 7/7] Update .changeset/kind-paws-try.md --- .changeset/kind-paws-try.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/kind-paws-try.md b/.changeset/kind-paws-try.md index ec0e86d994..4665e57721 100644 --- a/.changeset/kind-paws-try.md +++ b/.changeset/kind-paws-try.md @@ -2,4 +2,4 @@ "react-router": patch --- -fix page twitching when scrolling with revalidation +Skip scroll restoration on useRevalidator() calls because they're not new locations