Skip to content

Commit 75fa2fb

Browse files
committed
fix typegen when same route is reused in multiple paths
1 parent 369b100 commit 75fa2fb

File tree

12 files changed

+781
-678
lines changed

12 files changed

+781
-678
lines changed

.changeset/gold-baboons-roll.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@react-router/dev": patch
3+
"react-router": patch
4+
---
5+
6+
Fix typegen when same route is used at multiple paths
7+
8+
For example, `routes/route.tsx` is used at 4 different paths here:
9+
10+
```ts
11+
import { type RouteConfig, route } from "@react-router/dev/routes";
12+
export default [
13+
route("base/:base", "routes/base.tsx", [
14+
route("home/:home", "routes/route.tsx", { id: "home" }),
15+
route("changelog/:changelog", "routes/route.tsx", { id: "changelog" }),
16+
route("splat/*", "routes/route.tsx", { id: "splat" }),
17+
]),
18+
route("other/:other", "routes/route.tsx", { id: "other" }),
19+
] satisfies RouteConfig;
20+
```
21+
22+
Previously, typegen would arbitrarily pick one of these paths to be the "winner" and generate types for the route module based on that path.
23+
Now, typegen creates unions as necessary for alternate paths for the same route file.

integration/typegen-test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,4 +610,86 @@ test.describe("typegen", () => {
610610
expect(proc.status).toBe(0);
611611
});
612612
});
613+
614+
test("reuse route file at multiple paths", async () => {
615+
const cwd = await createProject({
616+
"vite.config.ts": viteConfig,
617+
"app/expect-type.ts": expectType,
618+
"app/routes.ts": tsx`
619+
import { type RouteConfig, route } from "@react-router/dev/routes";
620+
export default [
621+
route("base/:base", "routes/base.tsx", [
622+
route("home/:home", "routes/route.tsx", { id: "home" }),
623+
route("changelog/:changelog", "routes/route.tsx", { id: "changelog" }),
624+
route("splat/*", "routes/route.tsx", { id: "splat" }),
625+
]),
626+
route("other/:other", "routes/route.tsx", { id: "other" })
627+
] satisfies RouteConfig;
628+
`,
629+
"app/routes/base.tsx": tsx`
630+
import { Outlet } from "react-router"
631+
import type { Route } from "./+types/base"
632+
633+
export function loader() {
634+
return { base: "hello" }
635+
}
636+
637+
export default function Component() {
638+
return (
639+
<>
640+
<h1>Layout</h1>
641+
<Outlet/>
642+
</>
643+
)
644+
}
645+
`,
646+
"app/routes/route.tsx": tsx`
647+
import type { Expect, Equal } from "../expect-type"
648+
import type { Route } from "./+types/route"
649+
650+
export function loader() {
651+
return { route: "world" }
652+
}
653+
654+
export default function Component({ params, matches }: Route.ComponentProps) {
655+
type Test = Expect<Equal<typeof params,
656+
| {
657+
base: string;
658+
home: string;
659+
changelog?: undefined;
660+
"*"?: undefined;
661+
other?: undefined;
662+
}
663+
| {
664+
base: string;
665+
home?: undefined;
666+
changelog: string;
667+
"*"?: undefined;
668+
other?: undefined;
669+
}
670+
| {
671+
base: string;
672+
home?: undefined;
673+
changelog?: undefined;
674+
"*": string;
675+
other?: undefined;
676+
}
677+
| {
678+
base?: undefined;
679+
home?: undefined;
680+
changelog?: undefined;
681+
"*"?: undefined;
682+
other: string;
683+
}
684+
>>
685+
return <h1>Hello, world!</h1>
686+
}
687+
`,
688+
});
689+
690+
const proc = typecheck(cwd);
691+
expect(proc.stdout.toString()).toBe("");
692+
expect(proc.stderr.toString()).toBe("");
693+
expect(proc.status).toBe(0);
694+
});
613695
});

0 commit comments

Comments
 (0)