diff --git a/.changeset/kind-paws-try.md b/.changeset/kind-paws-try.md new file mode 100644 index 0000000000..4665e57721 --- /dev/null +++ b/.changeset/kind-paws-try.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Skip scroll restoration on useRevalidator() calls because they're not new locations 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 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", () => { 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; } diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 81cf0deffb..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,6 +1269,11 @@ export function createRouter(init: RouterInit): Router { blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER)); } + // 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 // 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, },