diff --git a/.changeset/scroll-restoration-action-redirect.md b/.changeset/scroll-restoration-action-redirect.md new file mode 100644 index 0000000000..186e3c8260 --- /dev/null +++ b/.changeset/scroll-restoration-action-redirect.md @@ -0,0 +1,5 @@ +--- +"@remix-run/router": patch +--- + +Fix scroll restoration when redirecting in an action diff --git a/packages/router/__tests__/router-test.ts b/packages/router/__tests__/router-test.ts index 39d2c8aac2..cc5eceabec 100644 --- a/packages/router/__tests__/router-test.ts +++ b/packages/router/__tests__/router-test.ts @@ -6485,6 +6485,72 @@ describe("a router", () => { expect(t.router.state.preventScrollReset).toBe(false); }); + it("restores scroll on submissions that redirect to the same location", async () => { + let t = setup({ + routes: SCROLL_ROUTES, + initialEntries: ["/tasks"], + hydrationData: { + loaderData: { + index: "INDEX_DATA", + }, + }, + }); + + expect(t.router.state.restoreScrollPosition).toBe(false); + expect(t.router.state.preventScrollReset).toBe(false); + // We were previously on tasks at 100 + let positions = { "/tasks": 100 }; + // But we've scrolled up to 50 to submit. We'll save this overtop of + // the 100 when we start this submission navigation and then restore to + // 50 below + let activeScrollPosition = 50; + t.router.enableScrollRestoration( + positions, + () => activeScrollPosition, + (l) => l.pathname + ); + + let nav1 = await t.navigate("/tasks", { + formMethod: "post", + formData: createFormData({}), + }); + const nav2 = await nav1.actions.tasks.redirectReturn("/tasks"); + await nav2.loaders.tasks.resolve("TASKS"); + expect(t.router.state.restoreScrollPosition).toBe(50); + expect(t.router.state.preventScrollReset).toBe(false); + }); + + it("restores scroll on submissions that redirect to new locations", async () => { + let t = setup({ + routes: SCROLL_ROUTES, + initialEntries: ["/tasks"], + hydrationData: { + loaderData: { + index: "INDEX_DATA", + }, + }, + }); + + expect(t.router.state.restoreScrollPosition).toBe(false); + expect(t.router.state.preventScrollReset).toBe(false); + let positions = { "/": 50, "/tasks": 100 }; + let activeScrollPosition = 0; + t.router.enableScrollRestoration( + positions, + () => activeScrollPosition, + (l) => l.pathname + ); + + let nav1 = await t.navigate("/tasks", { + formMethod: "post", + formData: createFormData({}), + }); + const nav2 = await nav1.actions.tasks.redirectReturn("/"); + await nav2.loaders.index.resolve("INDEX"); + expect(t.router.state.restoreScrollPosition).toBe(50); + expect(t.router.state.preventScrollReset).toBe(false); + }); + it("does not restore scroll on submissions", async () => { let t = setup({ routes: SCROLL_ROUTES, diff --git a/packages/router/router.ts b/packages/router/router.ts index 6365a49e58..b6cab2498e 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -771,6 +771,12 @@ export function createRouter(init: RouterInit): Router { ) : state.loaderData; + // Don't restore on submission navigations unless they redirect + let restoreScrollPosition = + state.navigation.formData && location.state?._isRedirect !== true + ? false + : getSavedScrollPosition(location, newState.matches || state.matches); + updateState({ ...newState, // matches, errors, fetchers go through as-is actionData, @@ -780,10 +786,7 @@ export function createRouter(init: RouterInit): Router { initialized: true, navigation: IDLE_NAVIGATION, revalidation: "idle", - // Don't restore on submission navigations - restoreScrollPosition: state.navigation.formData - ? false - : getSavedScrollPosition(location, newState.matches || state.matches), + restoreScrollPosition, preventScrollReset: pendingPreventScrollReset, });