Skip to content

Commit 0a6acb1

Browse files
pcattoripenspinner
andauthored
Do not serialize types for useRouteLoaderData<typeof clientLoader>() (#13752)
* test * do not serialize types for `useRouteLoaderData<typeof clientLoader()>` * Update .changeset/weak-turkeys-pretend.md Co-authored-by: Steven Liao <[email protected]> --------- Co-authored-by: Steven Liao <[email protected]>
1 parent 5309f45 commit 0a6acb1

File tree

4 files changed

+151
-83
lines changed

4 files changed

+151
-83
lines changed

.changeset/weak-turkeys-pretend.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Do not serialize types for `useRouteLoaderData<typeof clientLoader>`
6+
7+
For types to distinguish a `clientLoader` from a `serverLoader`, you MUST annotate `clientLoader` args:
8+
9+
```ts
10+
// 👇 annotation required to skip serializing types
11+
export function clientLoader({}: Route.ClientLoaderArgs) {
12+
return { fn: () => "earth" };
13+
}
14+
15+
function SomeComponent() {
16+
const data = useRouteLoaderData<typeof clientLoader>("routes/this-route");
17+
const planet = data?.fn() ?? "world";
18+
return <h1>Hello, {planet}!</h1>;
19+
}
20+
```

integration/typegen-test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,36 @@ test.describe("typegen", () => {
325325
expect(proc.status).toBe(0);
326326
});
327327

328+
test("clientLoader data should not be serialized", async () => {
329+
const cwd = await createProject({
330+
"vite.config.ts": viteConfig,
331+
"app/expect-type.ts": expectType,
332+
"app/routes/_index.tsx": tsx`
333+
import { useRouteLoaderData } from "react-router"
334+
335+
import type { Expect, Equal } from "../expect-type"
336+
import type { Route } from "./+types/_index"
337+
338+
export function clientLoader({}: Route.ClientLoaderArgs) {
339+
return { fn: () => 0 }
340+
}
341+
342+
export default function Component({ loaderData }: Route.ComponentProps) {
343+
type Test1 = Expect<Equal<typeof loaderData, { fn: () => number }>>
344+
345+
const routeLoaderData = useRouteLoaderData<typeof clientLoader>("routes/_index")
346+
type Test2 = Expect<Equal<typeof routeLoaderData, { fn: () => number} | undefined>>
347+
348+
return <h1>Hello, world!</h1>
349+
}
350+
`,
351+
});
352+
const proc = typecheck(cwd);
353+
expect(proc.stdout.toString()).toBe("");
354+
expect(proc.stderr.toString()).toBe("");
355+
expect(proc.status).toBe(0);
356+
});
357+
328358
test("custom app dir", async () => {
329359
const cwd = await createProject({
330360
"vite.config.ts": viteConfig,

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

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ import type {
22
ClientLoaderFunctionArgs,
33
ClientActionFunctionArgs,
44
} from "../dom/ssr/routeModules";
5-
import type { DataWithResponseInit } from "../router/utils";
5+
import type {
6+
DataWithResponseInit,
7+
unstable_RouterContextProvider,
8+
} from "../router/utils";
69
import type { Serializable } from "../server-runtime/single-fetch";
10+
import type { AppLoadContext } from "../server-runtime/data";
11+
12+
import type { MiddlewareEnabled } from "./future";
713
import type { RouteModule } from "./route-module";
814
import type { unstable_SerializesTo } from "./serializes-to";
915
import type { Equal, Expect, Func, IsAny, Pretty } from "./utils";
@@ -62,8 +68,80 @@ type ServerData<T> =
6268
export type ServerDataFrom<T> = ServerData<DataFrom<T>>;
6369
export type ClientDataFrom<T> = ClientData<DataFrom<T>>;
6470

71+
export type ClientDataFunctionArgs<Params> = {
72+
/**
73+
* A {@link https://developer.mozilla.org/en-US/docs/Web/API/Request Fetch Request instance} which you can use to read the URL, the method, the "content-type" header, and the request body from the request.
74+
*
75+
* @note Because client data functions are called before a network request is made, the Request object does not include the headers which the browser automatically adds. React Router infers the "content-type" header from the enc-type of the form that performed the submission.
76+
**/
77+
request: Request;
78+
/**
79+
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
80+
* @example
81+
* // app/routes.ts
82+
* route("teams/:teamId", "./team.tsx"),
83+
*
84+
* // app/team.tsx
85+
* export function clientLoader({
86+
* params,
87+
* }: Route.ClientLoaderArgs) {
88+
* params.teamId;
89+
* // ^ string
90+
* }
91+
**/
92+
params: Params;
93+
/**
94+
* When `future.unstable_middleware` is not enabled, this is undefined.
95+
*
96+
* When `future.unstable_middleware` is enabled, this is an instance of
97+
* `unstable_RouterContextProvider` and can be used to access context values
98+
* from your route middlewares. You may pass in initial context values in your
99+
* `<HydratedRouter unstable_getContext>` prop
100+
*/
101+
context: unstable_RouterContextProvider;
102+
};
103+
104+
export type ServerDataFunctionArgs<Params> = {
105+
/** A {@link https://developer.mozilla.org/en-US/docs/Web/API/Request Fetch Request instance} which you can use to read the url, method, headers (such as cookies), and request body from the request. */
106+
request: Request;
107+
/**
108+
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
109+
* @example
110+
* // app/routes.ts
111+
* route("teams/:teamId", "./team.tsx"),
112+
*
113+
* // app/team.tsx
114+
* export function loader({
115+
* params,
116+
* }: Route.LoaderArgs) {
117+
* params.teamId;
118+
* // ^ string
119+
* }
120+
**/
121+
params: Params;
122+
/**
123+
* Without `future.unstable_middleware` enabled, this is the context passed in
124+
* to your server adapter's `getLoadContext` function. It's a way to bridge the
125+
* gap between the adapter's request/response API with your React Router app.
126+
* It is only applicable if you are using a custom server adapter.
127+
*
128+
* With `future.unstable_middleware` enabled, this is an instance of
129+
* `unstable_RouterContextProvider` and can be used for type-safe access to
130+
* context value set in your route middlewares. If you are using a custom
131+
* server adapter, you may provide an initial set of context values from your
132+
* `getLoadContext` function.
133+
*/
134+
context: MiddlewareEnabled extends true
135+
? unstable_RouterContextProvider
136+
: AppLoadContext;
137+
};
138+
65139
export type SerializeFrom<T> = T extends (...args: infer Args) => unknown
66-
? Args extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs]
140+
? Args extends [
141+
| ClientLoaderFunctionArgs
142+
| ClientActionFunctionArgs
143+
| ClientDataFunctionArgs<unknown>
144+
]
67145
? ClientDataFrom<T>
68146
: ServerDataFrom<T>
69147
: T;

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

Lines changed: 21 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { MetaDescriptor } from "../dom/ssr/routeModules";
22
import type { Location } from "../router/history";
33
import type { LinkDescriptor } from "../router/links";
4-
import type {
5-
unstable_MiddlewareNextFunction,
6-
unstable_RouterContextProvider,
7-
} from "../router/utils";
8-
import type { AppLoadContext } from "../server-runtime/data";
9-
import type { MiddlewareEnabled } from "./future";
4+
import type { unstable_MiddlewareNextFunction } from "../router/utils";
105

11-
import type { GetLoaderData, ServerDataFrom } from "./route-data";
6+
import type {
7+
ClientDataFunctionArgs,
8+
GetLoaderData,
9+
ServerDataFrom,
10+
ServerDataFunctionArgs,
11+
} from "./route-data";
1212
import type { RouteModule } from "./route-module";
1313
import type { Pretty } from "./utils";
1414

@@ -67,94 +67,34 @@ type HeadersArgs = {
6767
errorHeaders: Headers | undefined;
6868
};
6969

70-
type ClientDataFunctionArgs<T extends RouteInfo> = {
71-
/**
72-
* A {@link https://developer.mozilla.org/en-US/docs/Web/API/Request Fetch Request instance} which you can use to read the URL, the method, the "content-type" header, and the request body from the request.
73-
*
74-
* @note Because client data functions are called before a network request is made, the Request object does not include the headers which the browser automatically adds. React Router infers the "content-type" header from the enc-type of the form that performed the submission.
75-
**/
76-
request: Request;
77-
/**
78-
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
79-
* @example
80-
* // app/routes.ts
81-
* route("teams/:teamId", "./team.tsx"),
82-
*
83-
* // app/team.tsx
84-
* export function clientLoader({
85-
* params,
86-
* }: Route.ClientLoaderArgs) {
87-
* params.teamId;
88-
* // ^ string
89-
* }
90-
**/
91-
params: T["params"];
92-
/**
93-
* When `future.unstable_middleware` is not enabled, this is undefined.
94-
*
95-
* When `future.unstable_middleware` is enabled, this is an instance of
96-
* `unstable_RouterContextProvider` and can be used to access context values
97-
* from your route middlewares. You may pass in initial context values in your
98-
* `<HydratedRouter unstable_getContext>` prop
99-
*/
100-
context: unstable_RouterContextProvider;
101-
};
102-
103-
type ServerDataFunctionArgs<T extends RouteInfo> = {
104-
/** A {@link https://developer.mozilla.org/en-US/docs/Web/API/Request Fetch Request instance} which you can use to read the url, method, headers (such as cookies), and request body from the request. */
105-
request: Request;
106-
/**
107-
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
108-
* @example
109-
* // app/routes.ts
110-
* route("teams/:teamId", "./team.tsx"),
111-
*
112-
* // app/team.tsx
113-
* export function loader({
114-
* params,
115-
* }: Route.LoaderArgs) {
116-
* params.teamId;
117-
* // ^ string
118-
* }
119-
**/
120-
params: T["params"];
121-
/**
122-
* Without `future.unstable_middleware` enabled, this is the context passed in
123-
* to your server adapter's `getLoadContext` function. It's a way to bridge the
124-
* gap between the adapter's request/response API with your React Router app.
125-
* It is only applicable if you are using a custom server adapter.
126-
*
127-
* With `future.unstable_middleware` enabled, this is an instance of
128-
* `unstable_RouterContextProvider` and can be used for type-safe access to
129-
* context value set in your route middlewares. If you are using a custom
130-
* server adapter, you may provide an initial set of context values from your
131-
* `getLoadContext` function.
132-
*/
133-
context: MiddlewareEnabled extends true
134-
? unstable_RouterContextProvider
135-
: AppLoadContext;
136-
};
137-
13870
type CreateServerMiddlewareFunction<T extends RouteInfo> = (
139-
args: ServerDataFunctionArgs<T>,
71+
args: ServerDataFunctionArgs<T["params"]>,
14072
next: unstable_MiddlewareNextFunction<Response>
14173
) => MaybePromise<Response | void>;
14274

14375
type CreateClientMiddlewareFunction<T extends RouteInfo> = (
144-
args: ClientDataFunctionArgs<T>,
76+
args: ClientDataFunctionArgs<T["params"]>,
14577
next: unstable_MiddlewareNextFunction<undefined>
14678
) => MaybePromise<void>;
14779

148-
type CreateServerLoaderArgs<T extends RouteInfo> = ServerDataFunctionArgs<T>;
80+
type CreateServerLoaderArgs<T extends RouteInfo> = ServerDataFunctionArgs<
81+
T["params"]
82+
>;
14983

150-
type CreateClientLoaderArgs<T extends RouteInfo> = ClientDataFunctionArgs<T> & {
84+
type CreateClientLoaderArgs<T extends RouteInfo> = ClientDataFunctionArgs<
85+
T["params"]
86+
> & {
15187
/** This is an asynchronous function to get the data from the server loader for this route. On client-side navigations, this will make a {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API fetch} call to the React Router server loader. If you opt-into running your clientLoader on hydration, then this function will return the data that was already loaded on the server (via Promise.resolve). */
15288
serverLoader: () => Promise<ServerDataFrom<T["module"]["loader"]>>;
15389
};
15490

155-
type CreateServerActionArgs<T extends RouteInfo> = ServerDataFunctionArgs<T>;
91+
type CreateServerActionArgs<T extends RouteInfo> = ServerDataFunctionArgs<
92+
T["params"]
93+
>;
15694

157-
type CreateClientActionArgs<T extends RouteInfo> = ClientDataFunctionArgs<T> & {
95+
type CreateClientActionArgs<T extends RouteInfo> = ClientDataFunctionArgs<
96+
T["params"]
97+
> & {
15898
/** This is an asynchronous function that makes the {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API fetch} call to the React Router server action for this route. */
15999
serverAction: () => Promise<ServerDataFrom<T["module"]["action"]>>;
160100
};

0 commit comments

Comments
 (0)