diff --git a/.changeset/type-userouteloaderdata.md b/.changeset/type-userouteloaderdata.md new file mode 100644 index 00000000000..d6aa1b2061d --- /dev/null +++ b/.changeset/type-userouteloaderdata.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +Add generic type for `useRouteLoaderData()` diff --git a/packages/remix-react/__tests__/hook-types-test.tsx b/packages/remix-react/__tests__/hook-types-test.tsx index c0bf59063b0..28ffd592f00 100644 --- a/packages/remix-react/__tests__/hook-types-test.tsx +++ b/packages/remix-react/__tests__/hook-types-test.tsx @@ -3,25 +3,30 @@ import type { TypedResponse, } from "@remix-run/server-runtime"; -import type { useLoaderData } from "../components"; +import type { useLoaderData, useRouteLoaderData } from "../components"; function isEqual( arg: A extends B ? (B extends A ? true : false) : false ): void {} type LoaderData = ReturnType>; +type RouteLoaderData = ReturnType>; describe("useLoaderData", () => { it("supports plain data type", () => { type AppData = { hello: string }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports plain Response", () => { type Loader = (args: any) => Response; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("infers type regardless of redirect", () => { @@ -29,31 +34,41 @@ describe("useLoaderData", () => { args: any ) => TypedResponse<{ id: string }> | TypedResponse; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports Response-returning loader", () => { type Loader = (args: any) => TypedResponse<{ hello: string }>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports async Response-returning loader", () => { type Loader = (args: any) => Promise>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports data-returning loader", () => { type Loader = (args: any) => { hello: string }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports async data-returning loader", () => { type Loader = (args: any) => Promise<{ hello: string }>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); }); @@ -61,41 +76,53 @@ describe("type serializer", () => { it("converts Date to string", () => { type AppData = { hello: Date }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports custom toJSON", () => { type AppData = { toJSON(): { data: string[] } }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports recursion", () => { type AppData = { dob: Date; parent: AppData }; type SerializedAppData = { dob: string; parent: SerializedAppData }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports tuples and arrays", () => { type AppData = { arr: Date[]; tuple: [string, number, Date]; empty: [] }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { arr: string[]; tuple: [string, number, string]; empty: [] } >(true); + isEqual(true); }); it("transforms unserializables to null in arrays", () => { type AppData = [Function, symbol, undefined]; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("transforms unserializables to never in objects", () => { type AppData = { arg1: Function; arg2: symbol; arg3: undefined }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("supports class instances", () => { @@ -105,7 +132,9 @@ describe("type serializer", () => { } type Loader = (args: any) => TypedResponse; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("makes keys optional if the value is undefined", () => { @@ -115,7 +144,9 @@ describe("type serializer", () => { arg3: undefined; }; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual(true); + isEqual(true); }); it("allows data key in value", () => { @@ -131,7 +162,9 @@ describe("deferred type serializer", () => { args: any ) => TypedDeferredData<{ hello: string; lazy: Promise }>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual }>(true); + isEqual(true); }); it("supports asynchronous loader", () => { @@ -139,7 +172,9 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual }>(true); + isEqual(true); }); it("supports synchronous loader with deferred object result", () => { @@ -147,7 +182,9 @@ describe("deferred type serializer", () => { args: any ) => TypedDeferredData<{ hello: string; lazy: Promise<{ a: number }> }>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual }>(true); + isEqual(true); }); it("supports asynchronous loader with deferred object result", () => { @@ -157,7 +194,9 @@ describe("deferred type serializer", () => { TypedDeferredData<{ hello: string; lazy: Promise<{ a: number }> }> >; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual }>(true); + isEqual(true); }); it("converts Date to string", () => { @@ -167,7 +206,9 @@ describe("deferred type serializer", () => { TypedDeferredData<{ hello: Date; lazy: Promise<{ a: Date }> }> >; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual }>(true); + isEqual(true); }); it("supports custom toJSON", () => { @@ -178,10 +219,12 @@ describe("deferred type serializer", () => { TypedDeferredData<{ hello: AppData; lazy: Promise<{ a: AppData }> }> >; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { hello: { data: string[] }; lazy: Promise<{ a: { data: string[] } }> } >(true); + isEqual(true); }); it("supports recursion", () => { @@ -191,10 +234,12 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { hello: SerializedAppData; lazy: Promise } >(true); + isEqual(true); }); it("supports tuples and arrays", () => { @@ -208,10 +253,12 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { hello: SerializedAppData; lazy: Promise } >(true); + isEqual(true); }); it("transforms unserializables to null in arrays", () => { @@ -221,10 +268,12 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { hello: SerializedAppData; lazy: Promise } >(true); + isEqual(true); }); it("transforms unserializables to never in objects", () => { @@ -233,7 +282,9 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual }>(true); + isEqual(true); }); it("supports class instances", () => { @@ -245,10 +296,12 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { hello: { arg: string }; lazy: Promise<{ arg: string }> } >(true); + isEqual(true); }); it("makes keys optional if the value is undefined", () => { @@ -262,10 +315,12 @@ describe("deferred type serializer", () => { args: any ) => Promise }>>; type response = LoaderData; + type routeResponse = RouteLoaderData; isEqual< response, { hello: SerializedAppData; lazy: Promise } >(true); + isEqual(true); }); it("allows data key in value", () => { diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index c4077f16109..845667362dd 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -32,6 +32,7 @@ import { useFetchers as useFetchersRR, useActionData as useActionDataRR, useLoaderData as useLoaderDataRR, + useRouteLoaderData as useRouteLoaderDataRR, useMatches as useMatchesRR, useLocation, useNavigation, @@ -1271,6 +1272,15 @@ export function useLoaderData(): SerializeFrom { return useLoaderDataRR() as SerializeFrom; } +/** + * Returns the loaderData for the given routeId. + * + * @see https://remix.run/hooks/use-route-loader-data + */ +export function useRouteLoaderData(routeId: string): SerializeFrom | undefined { + return useRouteLoaderDataRR(routeId) as SerializeFrom | undefined; +} + /** * Returns the JSON parsed data from the current route's `action`. * diff --git a/packages/remix-react/index.tsx b/packages/remix-react/index.tsx index 930abb91337..967a78e40ba 100644 --- a/packages/remix-react/index.tsx +++ b/packages/remix-react/index.tsx @@ -33,7 +33,6 @@ export { useResolvedPath, useRevalidator, useRouteError, - useRouteLoaderData, useSearchParams, useSubmit, unstable_useBlocker, @@ -60,6 +59,7 @@ export { useFetcher, useFetchers, useLoaderData, + useRouteLoaderData, useMatches, useActionData, RemixContext as UNSAFE_RemixContext,