Skip to content

Commit 7d365ed

Browse files
authored
matches as component prop (#12291)
* matches as component prop * refactor: tidier typegen
1 parent 7d92685 commit 7d365ed

File tree

4 files changed

+79
-63
lines changed

4 files changed

+79
-63
lines changed

integration/typegen-test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ test.describe("typegen", () => {
230230
expect(proc.status).toBe(0);
231231
});
232232

233-
test("meta matches", async () => {
233+
test("matches", async () => {
234234
const cwd = await createProject({
235235
"vite.config.ts": viteConfig,
236236
"app/expect-type.ts": expectType,
@@ -289,6 +289,14 @@ test.describe("typegen", () => {
289289
type Test2 = Expect<Equal<typeof parent2.data, { parent2: number }>>
290290
return []
291291
}
292+
293+
export default function Component({ matches }: Route.ComponentProps) {
294+
const parent1 = matches[1]
295+
type Test1 = Expect<Equal<typeof parent1.data, { parent1: number }>>
296+
297+
const parent2 = matches[2]
298+
type Test2 = Expect<Equal<typeof parent2.data, { parent2: number }>>
299+
}
292300
`,
293301
});
294302
const proc = typecheck(cwd);

packages/react-router-dev/typegen/generate.ts

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import dedent from "dedent";
1+
import ts from "dedent";
22
import * as Path from "pathe";
33
import * as Pathe from "pathe/utils";
44

@@ -26,47 +26,43 @@ export function generate(ctx: Context, route: RouteManifestEntry): string {
2626
})
2727
.join("\n");
2828

29-
return dedent`
29+
return ts`
3030
// React Router generated types for route:
3131
// ${route.file}
3232
3333
import type * as T from "react-router/route-module"
3434
3535
${parentTypeImports}
36-
type Parents = [${parents.map((_, i) => `Parent${i}`).join(", ")}]
3736
38-
type Params = {${formatParamProperties(urlpath)}}
39-
40-
type RouteModule = typeof import("../${Pathe.filename(route.file)}")
41-
type LoaderData = T.CreateLoaderData<RouteModule>
42-
type ActionData = T.CreateActionData<RouteModule>
37+
type Module = typeof import("../${Pathe.filename(route.file)}")
4338
4439
export type Info = {
40+
parents: [${parents.map((_, i) => `Parent${i}`).join(", ")}],
4541
id: "${route.id}"
4642
file: "${route.file}"
4743
path: "${route.path}"
48-
params: Params
49-
loaderData: LoaderData
50-
actionData: ActionData
44+
params: {${formatParamProperties(urlpath)}}
45+
module: Module
46+
loaderData: T.CreateLoaderData<Module>
47+
actionData: T.CreateActionData<Module>
5148
}
5249
5350
export namespace Route {
54-
5551
export type LinkDescriptors = T.LinkDescriptors
5652
export type LinksFunction = () => LinkDescriptors
5753
58-
export type MetaArgs = T.CreateMetaArgs<Params, LoaderData, Parents>
54+
export type MetaArgs = T.CreateMetaArgs<Info>
5955
export type MetaDescriptors = T.MetaDescriptors
6056
export type MetaFunction = (args: MetaArgs) => MetaDescriptors
6157
62-
export type LoaderArgs = T.CreateServerLoaderArgs<Params>
63-
export type ClientLoaderArgs = T.CreateClientLoaderArgs<Params, RouteModule>
64-
export type ActionArgs = T.CreateServerActionArgs<Params>
65-
export type ClientActionArgs = T.CreateClientActionArgs<Params, RouteModule>
58+
export type LoaderArgs = T.CreateServerLoaderArgs<Info>
59+
export type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
60+
export type ActionArgs = T.CreateServerActionArgs<Info>
61+
export type ClientActionArgs = T.CreateClientActionArgs<Info>
6662
67-
export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Params>
68-
export type ComponentProps = T.CreateComponentProps<Params, LoaderData, ActionData>
69-
export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Params, LoaderData, ActionData>
63+
export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
64+
export type ComponentProps = T.CreateComponentProps<Info>
65+
export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>
7066
}
7167
`;
7268
}
@@ -87,24 +83,17 @@ function getRouteLineage(routes: RouteManifest, route: RouteManifestEntry) {
8783

8884
function formatParamProperties(urlpath: string) {
8985
const params = parseParams(urlpath);
90-
const indent = " ".repeat(3);
9186
const properties = Object.entries(params).map(([name, values]) => {
9287
if (values.length === 1) {
9388
const isOptional = values[0];
94-
return indent + (isOptional ? `"${name}"?: string` : `"${name}": string`);
89+
return isOptional ? `"${name}"?: string` : `"${name}": string`;
9590
}
9691
const items = values.map((isOptional) =>
9792
isOptional ? "string | undefined" : "string"
9893
);
99-
return indent + `"${name}": [${items.join(", ")}]`;
94+
return `"${name}": [${items.join(", ")}]`;
10095
});
101-
102-
// prettier-ignore
103-
const body =
104-
properties.length === 0 ? "" :
105-
"\n" + properties.join("\n") + "\n";
106-
107-
return body;
96+
return properties.join("; ");
10897
}
10998

11099
function parseParams(urlpath: string) {

packages/react-router-dev/vite/with-props.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ export const plugin: Plugin = {
1919
if (id !== VirtualModule.resolve(vmodId)) return;
2020
return dedent`
2121
import { createElement as h } from "react";
22-
import { useActionData, useLoaderData, useParams } from "react-router";
22+
import { useActionData, useLoaderData, useMatches, useParams } from "react-router";
2323
2424
export function withComponentProps(Component) {
2525
return function Wrapped() {
2626
const props = {
2727
params: useParams(),
2828
loaderData: useLoaderData(),
2929
actionData: useActionData(),
30+
matches: useMatches(),
3031
};
3132
return h(Component, props);
3233
};

packages/react-router/lib/types/route-module.ts

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ type RouteModule = {
2323
export type LinkDescriptors = LinkDescriptor[];
2424

2525
type RouteInfo = {
26+
parents: RouteInfo[];
27+
module: RouteModule;
2628
id: unknown;
29+
file: string;
30+
path: string;
2731
params: unknown;
2832
loaderData: unknown;
33+
actionData: unknown;
2934
};
3035

3136
type MetaMatch<T extends RouteInfo> = Pretty<
@@ -44,12 +49,12 @@ type MetaMatches<T extends RouteInfo[]> =
4449
? [MetaMatch<F>, ...MetaMatches<R>]
4550
: [];
4651

47-
export type CreateMetaArgs<Params, LoaderData, Parents extends RouteInfo[]> = {
52+
export type CreateMetaArgs<T extends RouteInfo> = {
4853
location: Location;
49-
params: Params;
50-
data: LoaderData;
54+
params: T["params"];
55+
data: T["loaderData"];
5156
error?: unknown;
52-
matches: MetaMatches<Parents>;
57+
matches: MetaMatches<T["parents"]>;
5358
};
5459
export type MetaDescriptors = MetaDescriptor[];
5560

@@ -95,48 +100,61 @@ type _CreateActionData<ServerActionData, ClientActionData> = Awaited<
95100
undefined
96101
>
97102

98-
type ClientDataFunctionArgs<Params> = {
103+
type ClientDataFunctionArgs<T extends RouteInfo> = {
99104
request: Request;
100-
params: Params;
105+
params: T["params"];
101106
};
102107

103-
type ServerDataFunctionArgs<Params> = ClientDataFunctionArgs<Params> & {
108+
type ServerDataFunctionArgs<T extends RouteInfo> = ClientDataFunctionArgs<T> & {
104109
context: AppLoadContext;
105110
};
106111

107-
export type CreateServerLoaderArgs<Params> = ServerDataFunctionArgs<Params>;
112+
export type CreateServerLoaderArgs<T extends RouteInfo> =
113+
ServerDataFunctionArgs<T>;
108114

109-
export type CreateClientLoaderArgs<
110-
Params,
111-
T extends RouteModule
112-
> = ClientDataFunctionArgs<Params> & {
113-
serverLoader: () => Promise<ServerDataFrom<T["loader"]>>;
114-
};
115+
export type CreateClientLoaderArgs<T extends RouteInfo> =
116+
ClientDataFunctionArgs<T> & {
117+
serverLoader: () => Promise<ServerDataFrom<T["module"]["loader"]>>;
118+
};
115119

116-
export type CreateServerActionArgs<Params> = ServerDataFunctionArgs<Params>;
120+
export type CreateServerActionArgs<T extends RouteInfo> =
121+
ServerDataFunctionArgs<T>;
117122

118-
export type CreateClientActionArgs<
119-
Params,
120-
T extends RouteModule
121-
> = ClientDataFunctionArgs<Params> & {
122-
serverAction: () => Promise<ServerDataFrom<T["action"]>>;
123-
};
123+
export type CreateClientActionArgs<T extends RouteInfo> =
124+
ClientDataFunctionArgs<T> & {
125+
serverAction: () => Promise<ServerDataFrom<T["module"]["action"]>>;
126+
};
124127

125-
export type CreateHydrateFallbackProps<Params> = {
126-
params: Params;
128+
export type CreateHydrateFallbackProps<T extends RouteInfo> = {
129+
params: T["params"];
127130
};
128131

129-
export type CreateComponentProps<Params, LoaderData, ActionData> = {
130-
params: Params;
131-
loaderData: LoaderData;
132-
actionData?: ActionData;
132+
type Match<T extends RouteInfo> = Pretty<
133+
Pick<T, "id" | "params"> & {
134+
pathname: string;
135+
data: T["loaderData"];
136+
handle: unknown;
137+
}
138+
>;
139+
140+
// prettier-ignore
141+
type Matches<T extends RouteInfo[]> =
142+
T extends [infer F extends RouteInfo, ...infer R extends RouteInfo[]]
143+
? [Match<F>, ...Matches<R>]
144+
: [];
145+
146+
export type CreateComponentProps<T extends RouteInfo> = {
147+
params: T["params"];
148+
loaderData: T["loaderData"];
149+
actionData?: T["actionData"];
150+
matches: Matches<T["parents"]>;
133151
};
134152

135-
export type CreateErrorBoundaryProps<Params, LoaderData, ActionData> = {
136-
params: Params;
153+
export type CreateErrorBoundaryProps<T extends RouteInfo> = {
154+
params: T["params"];
137155
error: unknown;
138-
loaderData?: LoaderData;
139-
actionData?: ActionData;
156+
loaderData?: T["loaderData"];
157+
actionData?: T["actionData"];
140158
};
141159

142160
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)