diff --git a/.changeset/long-peas-doubt.md b/.changeset/long-peas-doubt.md new file mode 100644 index 0000000000..67d9fa1952 --- /dev/null +++ b/.changeset/long-peas-doubt.md @@ -0,0 +1,15 @@ +--- +"react-router": major +--- + +Migrate Remix type generics to React Router + +- These generics are provided for Remix v2 migration purposes +- These generics and the APIs they exist on should be considered informally deprecated in favor of the new `Route.*` types +- Anyone migrating from React Router v6 should probably not leverage these new generics and should migrate straight to the `Route.*` types +- For React Router v6 users, these generics are new and should not impact your app, with one exception + - `useFetcher` previously had an optional generic (used primarily by Remix v2) that expected the data type + - This has been updated in v7 to expect the type of the function that generates the data (i.e., `typeof loader`/`typeof action`) + - Therefore, you should update your usages: + - ❌ `useFetcher()` + - ✅ `useFetcher()` diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx index 9aa96dd231..0e69f97fef 100644 --- a/packages/react-router/lib/components.tsx +++ b/packages/react-router/lib/components.tsx @@ -817,14 +817,14 @@ export function Routes({ return useRoutes(createRoutesFromChildren(children), location); } -export interface AwaitResolveRenderFunction { - (data: Awaited): React.ReactNode; +export interface AwaitResolveRenderFunction { + (data: Awaited): React.ReactNode; } /** * @category Types */ -export interface AwaitProps { +export interface AwaitProps { /** When using a function, the resolved value is provided as the parameter. @@ -923,7 +923,7 @@ export interface AwaitProps { } ``` */ - resolve: TrackedPromise | any; + resolve: Resolve; } /** @@ -967,7 +967,11 @@ function Book() { @category Components */ -export function Await({ children, errorElement, resolve }: AwaitProps) { +export function Await({ + children, + errorElement, + resolve, +}: AwaitProps) { return ( {children} diff --git a/packages/react-router/lib/dom/lib.tsx b/packages/react-router/lib/dom/lib.tsx index f2ecb3ee97..cc971d2361 100644 --- a/packages/react-router/lib/dom/lib.tsx +++ b/packages/react-router/lib/dom/lib.tsx @@ -88,6 +88,7 @@ import { useResolvedPath, useRouteId, } from "../hooks"; +import type { SerializeFrom } from "../types"; //////////////////////////////////////////////////////////////////////////////// //#region Global Stuff @@ -1792,7 +1793,7 @@ export type FetcherWithComponents = Fetcher & { @category Hooks */ -export function useFetcher({ +export function useFetcher({ key, }: { /** @@ -1813,7 +1814,7 @@ export function useFetcher({ ``` */ key?: string; -} = {}): FetcherWithComponents { +} = {}): FetcherWithComponents> { let { router } = useDataRouterContext(DataRouterHook.UseFetcher); let state = useDataRouterState(DataRouterStateHook.UseFetcher); let fetcherData = React.useContext(FetchersContext); diff --git a/packages/react-router/lib/dom/ssr/components.tsx b/packages/react-router/lib/dom/ssr/components.tsx index e91636138f..f56ea11cb3 100644 --- a/packages/react-router/lib/dom/ssr/components.tsx +++ b/packages/react-router/lib/dom/ssr/components.tsx @@ -32,9 +32,6 @@ import { useLocation } from "../../hooks"; import { getPartialManifest, isFogOfWarEnabled } from "./fog-of-war"; import type { PageLinkDescriptor } from "../../router/links"; -// TODO: Temporary shim until we figure out the way to handle typings in v7 -export type SerializeFrom = D extends () => {} ? Awaited> : D; - function useDataRouterContext() { let context = React.useContext(DataRouterContext); invariant( diff --git a/packages/react-router/lib/dom/ssr/routeModules.ts b/packages/react-router/lib/dom/ssr/routeModules.ts index 5c3500faa4..5e3f607bf0 100644 --- a/packages/react-router/lib/dom/ssr/routeModules.ts +++ b/packages/react-router/lib/dom/ssr/routeModules.ts @@ -9,10 +9,10 @@ import type { ShouldRevalidateFunction, } from "../../router/utils"; -import type { SerializeFrom } from "./components"; import type { EntryRoute } from "./routes"; import type { DataRouteMatch } from "../../context"; import type { LinkDescriptor } from "../../router/links"; +import type { SerializeFrom } from "../../types"; export interface RouteModules { [routeId: string]: RouteModule | undefined; @@ -96,11 +96,13 @@ export interface LinksFunction { export interface MetaMatch< RouteId extends string = string, - Loader extends LoaderFunction | unknown = unknown + Loader extends LoaderFunction | ClientLoaderFunction | unknown = unknown > { id: RouteId; pathname: DataRouteMatch["pathname"]; - data: Loader extends LoaderFunction ? SerializeFrom : unknown; + data: Loader extends LoaderFunction | ClientLoaderFunction + ? SerializeFrom + : unknown; handle?: RouteHandle; params: DataRouteMatch["params"]; meta: MetaDescriptor[]; @@ -108,10 +110,10 @@ export interface MetaMatch< } export type MetaMatches< - MatchLoaders extends Record = Record< + MatchLoaders extends Record< string, - unknown - > + LoaderFunction | ClientLoaderFunction | unknown + > = Record > = Array< { [K in keyof MatchLoaders]: MetaMatch< @@ -122,14 +124,16 @@ export type MetaMatches< >; export interface MetaArgs< - Loader extends LoaderFunction | unknown = unknown, - MatchLoaders extends Record = Record< + Loader extends LoaderFunction | ClientLoaderFunction | unknown = unknown, + MatchLoaders extends Record< string, - unknown - > + LoaderFunction | ClientLoaderFunction | unknown + > = Record > { data: - | (Loader extends LoaderFunction ? SerializeFrom : unknown) + | (Loader extends LoaderFunction | ClientLoaderFunction + ? SerializeFrom + : unknown) | undefined; params: Params; location: Location; @@ -188,11 +192,11 @@ export interface MetaArgs< * ``` */ export interface MetaFunction< - Loader extends LoaderFunction | unknown = unknown, - MatchLoaders extends Record = Record< + Loader extends LoaderFunction | ClientLoaderFunction | unknown = unknown, + MatchLoaders extends Record< string, - unknown - > + LoaderFunction | ClientLoaderFunction | unknown + > = Record > { (args: MetaArgs): MetaDescriptor[] | undefined; } diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 430e14674e..68e7b9a8a5 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -48,6 +48,7 @@ import { resolveTo, stripBasename, } from "./router/utils"; +import type { SerializeFrom } from "./types"; // TODO: Let's get this back to using an import map and development/production // condition once we get the rollup build replaced @@ -1082,10 +1083,10 @@ export function useMatches(): UIMatch[] { @category Hooks */ -export function useLoaderData(): unknown { +export function useLoaderData(): SerializeFrom { let state = useDataRouterState(DataRouterStateHook.UseLoaderData); let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData); - return state.loaderData[routeId]; + return state.loaderData[routeId] as SerializeFrom; } /** @@ -1115,9 +1116,11 @@ export function useLoaderData(): unknown { @category Hooks */ -export function useRouteLoaderData(routeId: string): unknown { +export function useRouteLoaderData( + routeId: string +): SerializeFrom | undefined { let state = useDataRouterState(DataRouterStateHook.UseRouteLoaderData); - return state.loaderData[routeId]; + return state.loaderData[routeId] as SerializeFrom | undefined; } /** @@ -1145,10 +1148,12 @@ export function useRouteLoaderData(routeId: string): unknown { @category Hooks */ -export function useActionData(): unknown { +export function useActionData(): SerializeFrom | undefined { let state = useDataRouterState(DataRouterStateHook.UseActionData); let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData); - return state.actionData ? state.actionData[routeId] : undefined; + return (state.actionData ? state.actionData[routeId] : undefined) as + | SerializeFrom + | undefined; } /** diff --git a/packages/react-router/lib/types.ts b/packages/react-router/lib/types.ts index 9198cf5244..0e1897df8a 100644 --- a/packages/react-router/lib/types.ts +++ b/packages/react-router/lib/types.ts @@ -1,3 +1,7 @@ +import type { + ClientLoaderFunctionArgs, + ClientActionFunctionArgs, +} from "./dom/ssr/routeModules"; import type { DataWithResponseInit } from "./router/utils"; import type { AppLoadContext } from "./server-runtime/data"; import type { Serializable } from "./server-runtime/single-fetch"; @@ -121,6 +125,17 @@ type Serialize = undefined +/** + * @deprecated Generics on data APIs such as `useLoaderData`, `useActionData`, + * `meta`, etc. are deprecated in favor of the `Route.*` types generated via + * `react-router typegen` + */ +export type SerializeFrom = T extends (...args: infer Args) => unknown + ? Args extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] + ? ClientData> + : ServerData> + : T; + export type CreateServerLoaderArgs = ServerDataFunctionArgs; export type CreateClientLoaderArgs<