diff --git a/.changeset/orange-insects-complain.md b/.changeset/orange-insects-complain.md new file mode 100644 index 0000000000..048ea8d393 --- /dev/null +++ b/.changeset/orange-insects-complain.md @@ -0,0 +1,5 @@ +--- +"@remix-run/router": patch +--- + +Do not short circuit on hash change only mutation submissions diff --git a/packages/router/__tests__/router-test.ts b/packages/router/__tests__/router-test.ts index 713deed006..58c900369d 100644 --- a/packages/router/__tests__/router-test.ts +++ b/packages/router/__tests__/router-test.ts @@ -819,63 +819,48 @@ const TASK_ROUTES: TestRouteObject[] = [ }, ]; -const TM_ROUTES = [ +const TM_ROUTES: TestRouteObject[] = [ { path: "", id: "root", - - module: "", hasErrorBoundary: true, loader: true, children: [ { path: "/", id: "index", - hasLoader: true, loader: true, action: true, - - module: "", }, { path: "/foo", id: "foo", loader: true, action: true, - - module: "", }, { path: "/foo/bar", id: "foobar", loader: true, action: true, - - module: "", }, { path: "/bar", id: "bar", loader: true, action: true, - - module: "", }, { path: "/baz", id: "baz", loader: true, action: true, - - module: "", }, { path: "/p/:param", id: "param", loader: true, action: true, - - module: "", }, ], }, @@ -1380,7 +1365,7 @@ describe("a router", () => { }); }); - it("does not load anything on hash change only", async () => { + it("does not load anything on hash change only navigations", async () => { let t = initializeTmTest(); expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" }); let A = await t.navigate("/#bar"); @@ -1388,6 +1373,60 @@ describe("a router", () => { expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" }); }); + it('does not load anything on hash change only empty
navigations', async () => { + let t = initializeTmTest(); + expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" }); + let A = await t.navigate("/#bar", { + formData: createFormData({}), + }); + expect(A.loaders.root.stub.mock.calls.length).toBe(0); + expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" }); + }); + + it('runs loaders on hash change only non-empty navigations', async () => { + let t = initializeTmTest(); + expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" }); + let A = await t.navigate("/#bar", { + formData: createFormData({ key: "value" }), + }); + await A.loaders.root.resolve("ROOT 2"); + await A.loaders.index.resolve("INDEX 2"); + expect(t.router.state.location.search).toBe("?key=value"); + expect(t.router.state.loaderData).toMatchObject({ + root: "ROOT 2", + index: "INDEX 2", + }); + }); + + it('runs action/loaders on hash change only navigations', async () => { + let t = initializeTmTest(); + let A = await t.navigate("/foo#bar"); + expect(t.router.state.navigation.state).toBe("loading"); + await A.loaders.foo.resolve("A"); + expect(t.router.state.loaderData).toMatchObject({ + root: "ROOT", + foo: "A", + }); + + // Submit while we have an active hash causing us to lose it + let B = await t.navigate("/foo", { + formMethod: "post", + formData: createFormData({}), + }); + expect(t.router.state.navigation.state).toBe("submitting"); + await B.actions.foo.resolve("ACTION"); + await B.loaders.root.resolve("ROOT 2"); + await B.loaders.foo.resolve("B"); + expect(t.router.state.navigation.state).toBe("idle"); + expect(t.router.state.actionData).toMatchObject({ + foo: "ACTION", + }); + expect(t.router.state.loaderData).toMatchObject({ + root: "ROOT 2", + foo: "B", + }); + }); + it("sets all right states on hash change only", async () => { let t = initializeTmTest(); let key = t.router.state.location.key; @@ -2396,7 +2435,6 @@ describe("a router", () => { children: expect.any(Array), id: "root", loader: expect.any(Function), - module: "", path: "", }, }, @@ -2473,7 +2511,6 @@ describe("a router", () => { children: expect.any(Array), id: "root", loader: expect.any(Function), - module: "", path: "", }, }, diff --git a/packages/router/router.ts b/packages/router/router.ts index 0924aa4f76..f975f11942 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -1121,8 +1121,13 @@ export function createRouter(init: RouterInit): Router { return; } - // Short circuit if it's only a hash change - if (isHashChangeOnly(state.location, location)) { + // Short circuit if it's only a hash change and not a mutation submission + // For example, on /page#hash and submit a which will + // default to a navigation to /page + if ( + isHashChangeOnly(state.location, location) && + !(opts && opts.submission && isMutationMethod(opts.submission.formMethod)) + ) { completeNavigation(location, { matches }); return; }