Skip to content

Commit e4c0a84

Browse files
committed
Update href() with new implementation
1 parent 5543835 commit e4c0a84

File tree

2 files changed

+29
-18
lines changed

2 files changed

+29
-18
lines changed

packages/react-router/__tests__/href-test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,26 @@ describe("href", () => {
99
expect(href("/a/:b", { b: "hello", z: "ignored" })).toBe("/a/hello");
1010
expect(href("/a/:b?", { b: "hello", z: "ignored" })).toBe("/a/hello");
1111
expect(href("/a/:b?")).toBe("/a");
12+
expect(href("/:b?")).toBe("/");
13+
expect(href("/a/:e-z", { "e-z": "hello" })).toBe("/a/hello");
1214
});
1315

1416
it("works with repeated params", () => {
1517
expect(href("/a/:b?/:b/:b?/:b", { b: "hello" })).toBe(
1618
"/a/hello/hello/hello/hello"
1719
);
20+
expect(href("/a/:c?/:b/:c?/:b", { b: "hello" })).toBe("/a/hello/hello");
1821
});
1922

2023
it("works with splats", () => {
2124
expect(href("/a/*", { "*": "b/c" })).toBe("/a/b/c");
25+
expect(href("/a/*", {})).toBe("/a");
26+
});
27+
28+
it("works with malformed splats", () => {
29+
// this is how packages\react-router\lib\router\utils.ts: compilePath() will handle these routes.
30+
expect(href("/a/z*", { "*": "b/c" })).toBe("/a/z/b/c");
31+
expect(href("/a/z*", {})).toBe("/a/z");
2232
});
2333

2434
it("throws when required params are missing", () => {

packages/react-router/lib/href.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,27 @@ export function href<Path extends keyof Args>(
2727
...args: Args[Path]
2828
): string {
2929
let params = args[0];
30-
return path
31-
.split("/")
32-
.map((segment) => {
33-
if (segment === "*") {
34-
return params ? params["*"] : undefined;
35-
}
36-
37-
const match = segment.match(/^:([\w-]+)(\?)?/);
38-
if (!match) return segment;
39-
const param = match[1];
30+
let result = path.replace(
31+
/\/:([\w-]+)(\?)?/g, // same regex as in .\router\utils.ts: compilePath().
32+
(_: string, param: string, isOptional) => {
4033
const value = params ? params[param] : undefined;
41-
42-
const isRequired = match[2] === undefined;
43-
if (isRequired && value === undefined) {
44-
throw Error(
34+
if (isOptional == null && value == null) {
35+
throw new Error(
4536
`Path '${path}' requires param '${param}' but it was not provided`
4637
);
4738
}
48-
return value;
49-
})
50-
.filter((segment) => segment !== undefined)
51-
.join("/");
39+
return value == null ? "" : "/" + value;
40+
}
41+
);
42+
43+
if (result.endsWith("*")) {
44+
// treat trailing splat the same way as compilePath, and force it to be as if it were `/*`.
45+
// `react-router typegen` will not generate the params for a malformed splat, causing a type error, but we can still do the correct thing here.
46+
result = result.slice(0, result.endsWith("/*") ? -2 : -1);
47+
if (params && params["*"] != null) {
48+
result += "/" + params["*"];
49+
}
50+
}
51+
52+
return result || "/";
5253
}

0 commit comments

Comments
 (0)