From a977be6f89fc22921a871c3e2e08ca29e406733b Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Fri, 3 May 2024 17:03:41 -0400 Subject: [PATCH 1/5] Typesafety for single-fetch: defineLoader and defineAction --- .changeset/rotten-geckos-yawn.md | 50 +++++++++++ packages/remix-cloudflare/index.ts | 2 + packages/remix-deno/index.ts | 2 + packages/remix-node/index.ts | 2 + packages/remix-react/future/single-fetch.d.ts | 86 +++++-------------- packages/remix-server-runtime/index.ts | 3 + packages/remix-server-runtime/reexport.ts | 2 + packages/remix-server-runtime/single-fetch.ts | 51 +++++++++++ 8 files changed, 134 insertions(+), 64 deletions(-) create mode 100644 .changeset/rotten-geckos-yawn.md diff --git a/.changeset/rotten-geckos-yawn.md b/.changeset/rotten-geckos-yawn.md new file mode 100644 index 00000000000..1d8cc4a978c --- /dev/null +++ b/.changeset/rotten-geckos-yawn.md @@ -0,0 +1,50 @@ +--- +"@remix-run/cloudflare": patch +"@remix-run/deno": patch +"@remix-run/node": patch +"@remix-run/react": patch +"@remix-run/server-runtime": patch +--- + +Typesafety for single-fetch + +`defineLoader` and `defineAction` are helpers for authoring `loader`s and `action`s. +They are identity functions; they don't modify your loader or action at runtime. +Rather, they exist solely for typesafety by providing types for args and by ensuring valid return types. + +```ts +export let loader = defineLoader(({ request }) => { + // ^? Request + return {a:1, b: () => 2} + // ^ type error: `b` is not serializable +}) +``` + +Note that `defineLoader` and `defineAction` are not technically necessary for defining loaders and actions if you aren't concerned with typesafety: + +```ts +// this totally works! and typechecking is happy too! +export let loader = () => { + return {a: 1} +} +``` + +This means that you can opt-in to `defineLoader` incrementally, one loader at a time. + +You can return custom responses via the `json`/`defer` utilities, but doing so will revert back to the old JSON-based typesafety mechanism: + +```ts +let loader1 = () => { + return {a: 1, b: new Date()} +} +let data1 = useLoaderData() +// ^? {a: number, b: Date} + +let loader2 = () => { + return json({a: 1, b: new Date()}) // this opts-out of turbo-stream +} +let data2 = useLoaderData() +// ^? JsonifyObject<{a: number, b: Date}> which is really {a: number, b: string} +``` + +You can also continue to return totally custom responses with `Response` though this continues to be outside of the typesystem since the built-in `Response` type is not generic diff --git a/packages/remix-cloudflare/index.ts b/packages/remix-cloudflare/index.ts index e9937361263..bc0954968a2 100644 --- a/packages/remix-cloudflare/index.ts +++ b/packages/remix-cloudflare/index.ts @@ -12,6 +12,8 @@ export { export { createRequestHandler, createSession, + defineLoader, + defineAction, defer, broadcastDevReady, logDevReady, diff --git a/packages/remix-deno/index.ts b/packages/remix-deno/index.ts index 08c67e64f07..79f8a0da096 100644 --- a/packages/remix-deno/index.ts +++ b/packages/remix-deno/index.ts @@ -16,6 +16,8 @@ export { export { broadcastDevReady, createSession, + defineLoader, + defineAction, defer, isCookie, isSession, diff --git a/packages/remix-node/index.ts b/packages/remix-node/index.ts index 1caccaf7ba0..9f0087c9908 100644 --- a/packages/remix-node/index.ts +++ b/packages/remix-node/index.ts @@ -24,6 +24,8 @@ export { export { createRequestHandler, createSession, + defineLoader, + defineAction, defer, broadcastDevReady, logDevReady, diff --git a/packages/remix-react/future/single-fetch.d.ts b/packages/remix-react/future/single-fetch.d.ts index ddef3d7098c..3436e077de1 100644 --- a/packages/remix-react/future/single-fetch.d.ts +++ b/packages/remix-react/future/single-fetch.d.ts @@ -1,7 +1,7 @@ import type { MetaArgs, UIMatch, UNSAFE_MetaMatch } from "@remix-run/react"; import type { - LoaderFunctionArgs, - ActionFunctionArgs, + Loader, + Action, SerializeFrom, TypedDeferredData, TypedResponse, @@ -11,90 +11,52 @@ import type { FetcherWithComponents, } from "react-router-dom"; -type Serializable = - | undefined - | null - | boolean - | string - | symbol - | number - | Array - | { [key: PropertyKey]: Serializable } - | bigint - | Date - | URL - | RegExp - | Error - | Map - | Set - | Promise; - -type DataFunctionReturnValue = - | Serializable - | TypedDeferredData> - | TypedResponse>; - -type LoaderFunction_SingleFetch = ( - args: LoaderFunctionArgs -) => Promise | DataFunctionReturnValue; -type ActionFunction_SingleFetch = ( - args: ActionFunctionArgs -) => Promise | DataFunctionReturnValue; - // Backwards-compatible type for Remix v2 where json/defer still use the old types, // and only non-json/defer returns use the new types. This allows for incremental // migration of loaders to return naked objects. In the next major version, // json/defer will be removed so everything will use the new simplified typings. // prettier-ignore -type SingleFetchSerialize_V2 = +type Serialize = Awaited> extends TypedDeferredData ? D : Awaited> extends TypedResponse> ? SerializeFrom : Awaited>; declare module "@remix-run/react" { - export function useLoaderData(): T extends LoaderFunction_SingleFetch - ? SingleFetchSerialize_V2 - : never; + export function useLoaderData(): T extends Loader ? Serialize : T; - export function useActionData(): T extends ActionFunction_SingleFetch - ? SingleFetchSerialize_V2 | undefined - : never; + export function useActionData(): T extends Action + ? Serialize | undefined + : T; export function useRouteLoaderData( routeId: string - ): T extends LoaderFunction_SingleFetch ? SingleFetchSerialize_V2 : never; + ): T extends Loader ? Serialize : never; export function useFetcher( opts?: Parameters[0] ): FetcherWithComponents< - TData extends LoaderFunction_SingleFetch | ActionFunction_SingleFetch - ? SingleFetchSerialize_V2 - : never + TData extends Loader | Action ? Serialize : TData >; export type UIMatch_SingleFetch = Omit< UIMatch, "data" > & { - data: D extends LoaderFunction_SingleFetch - ? SingleFetchSerialize_V2 - : never; + data: D extends Loader ? Serialize : never; }; interface MetaMatch_SingleFetch< RouteId extends string = string, - Loader extends LoaderFunction_SingleFetch | unknown = unknown - > extends Omit, "data"> { - data: Loader extends LoaderFunction_SingleFetch - ? SingleFetchSerialize_V2 - : unknown; + L extends Loader | unknown = unknown + > extends Omit, "data"> { + data: L extends Loader ? Serialize : unknown; } type MetaMatches_SingleFetch< - MatchLoaders extends Record< + MatchLoaders extends Record = Record< string, - LoaderFunction_SingleFetch | unknown - > = Record + unknown + > > = Array< { [K in keyof MatchLoaders]: MetaMatch_SingleFetch< @@ -105,17 +67,13 @@ declare module "@remix-run/react" { >; export interface MetaArgs_SingleFetch< - Loader extends LoaderFunction_SingleFetch | unknown = unknown, - MatchLoaders extends Record< + L extends Loader | unknown = unknown, + MatchLoaders extends Record = Record< string, - LoaderFunction_SingleFetch | unknown - > = Record - > extends Omit, "data" | "matches"> { - data: - | (Loader extends LoaderFunction_SingleFetch - ? SingleFetchSerialize_V2 - : unknown) - | undefined; + unknown + > + > extends Omit, "data" | "matches"> { + data: (L extends Loader ? Serialize : unknown) | undefined; matches: MetaMatches_SingleFetch; } } diff --git a/packages/remix-server-runtime/index.ts b/packages/remix-server-runtime/index.ts index 7744129cdb7..2c44d4cdc27 100644 --- a/packages/remix-server-runtime/index.ts +++ b/packages/remix-server-runtime/index.ts @@ -6,6 +6,8 @@ export { } from "./formData"; export { defer, json, redirect, redirectDocument } from "./responses"; export type { + Loader, + Action, SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch"; @@ -85,3 +87,4 @@ export type { UploadHandler, UploadHandlerPart, } from "./reexport"; +export { defineLoader, defineAction } from "./reexport"; diff --git a/packages/remix-server-runtime/reexport.ts b/packages/remix-server-runtime/reexport.ts index cc7fc46f507..2a0316600b5 100644 --- a/packages/remix-server-runtime/reexport.ts +++ b/packages/remix-server-runtime/reexport.ts @@ -61,3 +61,5 @@ export type { SessionStorage, FlashSessionData, } from "./sessions"; + +export { defineLoader, defineAction } from "./single-fetch"; diff --git a/packages/remix-server-runtime/single-fetch.ts b/packages/remix-server-runtime/single-fetch.ts index c9303e79be0..3104eb62b4f 100644 --- a/packages/remix-server-runtime/single-fetch.ts +++ b/packages/remix-server-runtime/single-fetch.ts @@ -1,4 +1,6 @@ import type { + ActionFunctionArgs as RRActionArgs, + LoaderFunctionArgs as RRLoaderArgs, StaticHandler, unstable_DataStrategyFunctionArgs as DataStrategyFunctionArgs, unstable_DataStrategyFunction as DataStrategyFunction, @@ -19,6 +21,7 @@ import type { ResponseStubOperation, } from "./routeModules"; import { ResponseStubOperationsSymbol } from "./routeModules"; +import type { TypedDeferredData, TypedResponse } from "./responses"; import { isDeferredData, isRedirectStatusCode, isResponse } from "./responses"; export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); @@ -504,3 +507,51 @@ export function encodeViaTurboStream( ], }); } + +type MaybePromise = T | Promise; + +type Serializable = + | undefined + | null + | boolean + | string + | symbol + | number + | Array + | { [key: PropertyKey]: Serializable } + | bigint + | Date + | URL + | RegExp + | Error + | Map + | Set + | Promise; + +type DataFunctionReturnValue = + | Serializable + | TypedDeferredData> + | TypedResponse>; + +type LoaderArgs = RRLoaderArgs & { + // Context is always provided in Remix, and typed for module augmentation support. + context: AppLoadContext; + response: ResponseStub; +}; + +export type Loader = ( + args: LoaderArgs +) => MaybePromise; + +type ActionArgs = RRActionArgs & { + // Context is always provided in Remix, and typed for module augmentation support. + context: AppLoadContext; + response: ResponseStub; +}; + +export type Action = ( + args: ActionArgs +) => MaybePromise; + +export let defineLoader = (loader: T): T => loader; +export let defineAction = (action: T): T => action; From ab4a44d8c639990fbd858c46db8422c5613888b5 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Tue, 7 May 2024 11:09:40 -0400 Subject: [PATCH 2/5] pr feedback --- packages/remix-cloudflare/index.ts | 4 ++-- packages/remix-deno/index.ts | 4 ++-- packages/remix-node/index.ts | 4 ++-- packages/remix-react/future/single-fetch.d.ts | 16 ++++++---------- packages/remix-server-runtime/index.ts | 5 ++++- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/remix-cloudflare/index.ts b/packages/remix-cloudflare/index.ts index bc0954968a2..a21c55b7516 100644 --- a/packages/remix-cloudflare/index.ts +++ b/packages/remix-cloudflare/index.ts @@ -12,8 +12,8 @@ export { export { createRequestHandler, createSession, - defineLoader, - defineAction, + unstable_defineLoader, + unstable_defineAction, defer, broadcastDevReady, logDevReady, diff --git a/packages/remix-deno/index.ts b/packages/remix-deno/index.ts index 79f8a0da096..bd91abef915 100644 --- a/packages/remix-deno/index.ts +++ b/packages/remix-deno/index.ts @@ -16,8 +16,8 @@ export { export { broadcastDevReady, createSession, - defineLoader, - defineAction, + unstable_defineLoader, + unstable_defineAction, defer, isCookie, isSession, diff --git a/packages/remix-node/index.ts b/packages/remix-node/index.ts index 9f0087c9908..eaf64599f3c 100644 --- a/packages/remix-node/index.ts +++ b/packages/remix-node/index.ts @@ -24,8 +24,8 @@ export { export { createRequestHandler, createSession, - defineLoader, - defineAction, + unstable_defineLoader, + unstable_defineAction, defer, broadcastDevReady, logDevReady, diff --git a/packages/remix-react/future/single-fetch.d.ts b/packages/remix-react/future/single-fetch.d.ts index 3436e077de1..d5be4e5e110 100644 --- a/packages/remix-react/future/single-fetch.d.ts +++ b/packages/remix-react/future/single-fetch.d.ts @@ -22,21 +22,17 @@ type Serialize = Awaited>; declare module "@remix-run/react" { - export function useLoaderData(): T extends Loader ? Serialize : T; + export function useLoaderData(): Serialize; - export function useActionData(): T extends Action - ? Serialize | undefined - : T; + export function useActionData(): Serialize | undefined; - export function useRouteLoaderData( + export function useRouteLoaderData( routeId: string - ): T extends Loader ? Serialize : never; + ): Serialize; - export function useFetcher( + export function useFetcher( opts?: Parameters[0] - ): FetcherWithComponents< - TData extends Loader | Action ? Serialize : TData - >; + ): FetcherWithComponents>; export type UIMatch_SingleFetch = Omit< UIMatch, diff --git a/packages/remix-server-runtime/index.ts b/packages/remix-server-runtime/index.ts index 2c44d4cdc27..ac614c95313 100644 --- a/packages/remix-server-runtime/index.ts +++ b/packages/remix-server-runtime/index.ts @@ -87,4 +87,7 @@ export type { UploadHandler, UploadHandlerPart, } from "./reexport"; -export { defineLoader, defineAction } from "./reexport"; +export { + defineLoader as unstable_defineLoader, + defineAction as unstable_defineAction, +} from "./reexport"; From 54693f33b38e5b3ab20a0aa9fc56a48b579e5a6a Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 8 May 2024 09:23:43 -0400 Subject: [PATCH 3/5] defineClientLoader and defineClientAction --- .changeset/rotten-geckos-yawn.md | 2 +- packages/remix-cloudflare/index.ts | 2 ++ packages/remix-deno/index.ts | 2 ++ packages/remix-node/index.ts | 2 ++ packages/remix-react/future/single-fetch.d.ts | 18 ++-------- packages/remix-server-runtime/index.ts | 19 +++++++---- packages/remix-server-runtime/reexport.ts | 2 -- packages/remix-server-runtime/single-fetch.ts | 34 ++++++++++++++++--- 8 files changed, 52 insertions(+), 29 deletions(-) diff --git a/.changeset/rotten-geckos-yawn.md b/.changeset/rotten-geckos-yawn.md index 1d8cc4a978c..72862e112a7 100644 --- a/.changeset/rotten-geckos-yawn.md +++ b/.changeset/rotten-geckos-yawn.md @@ -6,7 +6,7 @@ "@remix-run/server-runtime": patch --- -Typesafety for single-fetch +Typesafety for single-fetch: defineLoader, defineClientLoader, defineAction, defineClientAction `defineLoader` and `defineAction` are helpers for authoring `loader`s and `action`s. They are identity functions; they don't modify your loader or action at runtime. diff --git a/packages/remix-cloudflare/index.ts b/packages/remix-cloudflare/index.ts index a21c55b7516..d1463dbcaf7 100644 --- a/packages/remix-cloudflare/index.ts +++ b/packages/remix-cloudflare/index.ts @@ -13,7 +13,9 @@ export { createRequestHandler, createSession, unstable_defineLoader, + unstable_defineClientLoader, unstable_defineAction, + unstable_defineClientAction, defer, broadcastDevReady, logDevReady, diff --git a/packages/remix-deno/index.ts b/packages/remix-deno/index.ts index bd91abef915..160c21d9d50 100644 --- a/packages/remix-deno/index.ts +++ b/packages/remix-deno/index.ts @@ -17,7 +17,9 @@ export { broadcastDevReady, createSession, unstable_defineLoader, + unstable_defineClientLoader, unstable_defineAction, + unstable_defineClientAction, defer, isCookie, isSession, diff --git a/packages/remix-node/index.ts b/packages/remix-node/index.ts index eaf64599f3c..e1587d6dcbc 100644 --- a/packages/remix-node/index.ts +++ b/packages/remix-node/index.ts @@ -25,7 +25,9 @@ export { createRequestHandler, createSession, unstable_defineLoader, + unstable_defineClientLoader, unstable_defineAction, + unstable_defineClientAction, defer, broadcastDevReady, logDevReady, diff --git a/packages/remix-react/future/single-fetch.d.ts b/packages/remix-react/future/single-fetch.d.ts index d5be4e5e110..fa37a8a338b 100644 --- a/packages/remix-react/future/single-fetch.d.ts +++ b/packages/remix-react/future/single-fetch.d.ts @@ -1,26 +1,14 @@ import type { MetaArgs, UIMatch, UNSAFE_MetaMatch } from "@remix-run/react"; import type { - Loader, - Action, - SerializeFrom, - TypedDeferredData, - TypedResponse, + unstable_Loader as Loader, + unstable_Action as Action, + unstable_Serialize as Serialize, } from "@remix-run/server-runtime"; import type { useFetcher as useFetcherRR, FetcherWithComponents, } from "react-router-dom"; -// Backwards-compatible type for Remix v2 where json/defer still use the old types, -// and only non-json/defer returns use the new types. This allows for incremental -// migration of loaders to return naked objects. In the next major version, -// json/defer will be removed so everything will use the new simplified typings. -// prettier-ignore -type Serialize = - Awaited> extends TypedDeferredData ? D : - Awaited> extends TypedResponse> ? SerializeFrom : - Awaited>; - declare module "@remix-run/react" { export function useLoaderData(): Serialize; diff --git a/packages/remix-server-runtime/index.ts b/packages/remix-server-runtime/index.ts index ac614c95313..2840bdc6a33 100644 --- a/packages/remix-server-runtime/index.ts +++ b/packages/remix-server-runtime/index.ts @@ -5,13 +5,22 @@ export { parseMultipartFormData as unstable_parseMultipartFormData, } from "./formData"; export { defer, json, redirect, redirectDocument } from "./responses"; + +export { + SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, + defineLoader as unstable_defineLoader, + defineClientLoader as unstable_defineClientLoader, + defineAction as unstable_defineAction, + defineClientAction as unstable_defineClientAction, +} from "./single-fetch"; export type { - Loader, - Action, + Loader as unstable_Loader, + Action as unstable_Action, + Serialize as unstable_Serialize, SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch"; -export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol } from "./single-fetch"; + export { createRequestHandler } from "./server"; export { createSession, @@ -87,7 +96,3 @@ export type { UploadHandler, UploadHandlerPart, } from "./reexport"; -export { - defineLoader as unstable_defineLoader, - defineAction as unstable_defineAction, -} from "./reexport"; diff --git a/packages/remix-server-runtime/reexport.ts b/packages/remix-server-runtime/reexport.ts index 2a0316600b5..cc7fc46f507 100644 --- a/packages/remix-server-runtime/reexport.ts +++ b/packages/remix-server-runtime/reexport.ts @@ -61,5 +61,3 @@ export type { SessionStorage, FlashSessionData, } from "./sessions"; - -export { defineLoader, defineAction } from "./single-fetch"; diff --git a/packages/remix-server-runtime/single-fetch.ts b/packages/remix-server-runtime/single-fetch.ts index 3104eb62b4f..1ba3e2bb771 100644 --- a/packages/remix-server-runtime/single-fetch.ts +++ b/packages/remix-server-runtime/single-fetch.ts @@ -23,6 +23,7 @@ import type { import { ResponseStubOperationsSymbol } from "./routeModules"; import type { TypedDeferredData, TypedResponse } from "./responses"; import { isDeferredData, isRedirectStatusCode, isResponse } from "./responses"; +import type { SerializeFrom } from "./serialize"; export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); const ResponseStubActionSymbol = Symbol("ResponseStubAction"); @@ -533,25 +534,50 @@ type DataFunctionReturnValue = | TypedDeferredData> | TypedResponse>; +// Backwards-compatible type for Remix v2 where json/defer still use the old types, +// and only non-json/defer returns use the new types. This allows for incremental +// migration of loaders to return naked objects. In the next major version, +// json/defer will be removed so everything will use the new simplified typings. +// prettier-ignore +export type Serialize = + Awaited> extends TypedDeferredData ? D : + Awaited> extends TypedResponse> ? SerializeFrom : + Awaited>; + +// loader type LoaderArgs = RRLoaderArgs & { // Context is always provided in Remix, and typed for module augmentation support. context: AppLoadContext; response: ResponseStub; }; - export type Loader = ( args: LoaderArgs ) => MaybePromise; +export let defineLoader = (loader: T): T => loader; +// clientLoader +type ClientLoaderArgs = RRLoaderArgs & { + serverLoader: () => Promise>; +}; +type ClientLoader = (args: ClientLoaderArgs) => MaybePromise; +export let defineClientLoader = (clientLoader: T): T => + clientLoader; + +// action type ActionArgs = RRActionArgs & { // Context is always provided in Remix, and typed for module augmentation support. context: AppLoadContext; response: ResponseStub; }; - export type Action = ( args: ActionArgs ) => MaybePromise; - -export let defineLoader = (loader: T): T => loader; export let defineAction = (action: T): T => action; + +// clientAction +type ClientActionArgs = RRActionArgs & { + serverAction: () => Promise>; +}; +type ClientAction = (args: ClientActionArgs) => MaybePromise; +export let defineClientAction = (clientAction: T): T => + clientAction; From 4e0d411ab49b712628c2a9afafadc90a28525da5 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 8 May 2024 09:42:05 -0400 Subject: [PATCH 4/5] docs for single-fetch typesafety utils --- docs/guides/single-fetch.md | 61 ++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/guides/single-fetch.md b/docs/guides/single-fetch.md index 86bd2b590a9..8a7d396dbcc 100644 --- a/docs/guides/single-fetch.md +++ b/docs/guides/single-fetch.md @@ -98,13 +98,66 @@ In order to ensure you get the proper types when using Single Fetch, we've inclu ```json { - "include": [ - // ... - "node_modules/@remix-run/react/future/single-fetch.d.ts" - ] + "compilerOptions": { + "types": ["@remix-run/react/future/single-fetch.d.ts"] + } } ``` +🚨 Make sure the single-fetch types come after any other Remix packages in `types` so that they override those existing types. + +** `defineLoader`, `defineAction`, `defineClientLoader`, `defineClientAction` ** + +To get typesafety when defining loaders and actions, you can use the `defineLoader` and `defineAction` utilities: + +```ts +import { defineLoader } from "@remix-run/node"; + +export const loader = defineLoader(({ request }) => { + // ^? Request +}); + +export const action = defineAction(({ context }) => { + // ^? AppLoadContext +}); +``` + +Not only does this give you types for any arguments, but it also ensures you are returning single-fetch compatible types: + +```ts +export const loader = defineLoader(() => { + return { hello: "world", badData: () => 1 }; + // ^^^^^^^ Type error: `badData` is not serializable +}); + +export const action = defineAction(() => { + return { hello: "world", badData: new CustomType() }; + // ^^^^^^^ Type error: `badData` is not serializable +}); +``` + +Single-fetch supports the following return types: + +```ts +type Serializable = + | undefined + | null + | boolean + | string + | symbol + | number + | bigint + | Date + | URL + | RegExp + | Error + | Array + | { [key: PropertyKey]: Serializable } // objects with serializable values + | Map + | Set + | Promise; +``` + **`useLoaderData`, `useActionData`, `useRouteLoaderData`, and `useFetcher`** These methods do not require any code changes on your part - adding the single fetch types will cause their generics to deserialize correctly: From cdbea6269083bde3d8bbab1d0ee9b1dd8554f5d7 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 8 May 2024 19:44:35 -0400 Subject: [PATCH 5/5] pr feedback: hydrate options for client loader --- packages/remix-server-runtime/single-fetch.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/remix-server-runtime/single-fetch.ts b/packages/remix-server-runtime/single-fetch.ts index 1ba3e2bb771..a0dd631dc20 100644 --- a/packages/remix-server-runtime/single-fetch.ts +++ b/packages/remix-server-runtime/single-fetch.ts @@ -560,8 +560,9 @@ type ClientLoaderArgs = RRLoaderArgs & { serverLoader: () => Promise>; }; type ClientLoader = (args: ClientLoaderArgs) => MaybePromise; -export let defineClientLoader = (clientLoader: T): T => - clientLoader; +export let defineClientLoader = ( + clientLoader: T +): T & { hydrate?: boolean } => clientLoader; // action type ActionArgs = RRActionArgs & {