Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/orange-insects-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/router": patch
---

Do not short circuit on hash change only mutation submissions
75 changes: 56 additions & 19 deletions packages/router/__tests__/router-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
},
],
},
Expand Down Expand Up @@ -1380,14 +1365,68 @@ describe("a router", () => {
});
});

it("does not load anything on hash change only", async () => {
it("does not load anything on hash change only <Link> navigations", async () => {
let t = initializeTmTest();
expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" });
let A = await t.navigate("/#bar");
expect(A.loaders.root.stub.mock.calls.length).toBe(0);
expect(t.router.state.loaderData).toMatchObject({ root: "ROOT" });
});

it('does not load anything on hash change only empty <Form method="get"> 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 <Form method="get"> 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 <Form method="post"> 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;
Expand Down Expand Up @@ -2396,7 +2435,6 @@ describe("a router", () => {
children: expect.any(Array),
id: "root",
loader: expect.any(Function),
module: "",
path: "",
},
},
Expand Down Expand Up @@ -2473,7 +2511,6 @@ describe("a router", () => {
children: expect.any(Array),
id: "root",
loader: expect.any(Function),
module: "",
path: "",
},
},
Expand Down
9 changes: 7 additions & 2 deletions packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Form method="post"> which will
// default to a navigation to /page
if (
isHashChangeOnly(state.location, location) &&
!(opts && opts.submission && isMutationMethod(opts.submission.formMethod))
) {
completeNavigation(location, { matches });
return;
}
Expand Down