diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 37697ee647..8bc3665737 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -46,6 +46,7 @@ import { RouteContext, RouteErrorContext, } from "./context"; +import type { Action, Data, Loader } from "./serialize"; /** * Returns the full href for the given "to" value. This is useful for building @@ -925,7 +926,7 @@ export function useMatches(): UIMatch[] { /** * Returns the loader data for the nearest ancestor Route loader */ -export function useLoaderData(): unknown { +export function useLoaderData(): Data { let state = useDataRouterState(DataRouterStateHook.UseLoaderData); let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData); @@ -933,7 +934,7 @@ export function useLoaderData(): unknown { console.error( `You cannot \`useLoaderData\` in an errorElement (routeId: ${routeId})` ); - return undefined; + return undefined as any; } return state.loaderData[routeId]; } @@ -949,7 +950,7 @@ export function useRouteLoaderData(routeId: string): unknown { /** * Returns the action data for the nearest ancestor Route action */ -export function useActionData(): unknown { +export function useActionData(): Data { let state = useDataRouterState(DataRouterStateHook.UseActionData); let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData); return state.actionData ? state.actionData[routeId] : undefined; diff --git a/packages/react-router/lib/serialize.ts b/packages/react-router/lib/serialize.ts new file mode 100644 index 0000000000..5827b5ecb9 --- /dev/null +++ b/packages/react-router/lib/serialize.ts @@ -0,0 +1,53 @@ +type Serializable = + | undefined + | null + | boolean + | string + | symbol + | number + | Array + | { [key: PropertyKey]: Serializable } + | bigint + | Date + | URL + | RegExp + | Error + | Map + | Set + | Promise; + +export type Params = { + readonly [key in Key]: string | undefined; +}; + +// TODO: rename to `Context` or `DataContext` or `AppContext`? +interface AppLoadContext {} + +type MaybePromise = T | Promise; + +type DataFunction = ( + args: { + request: Request; + params: Params; + context?: AppLoadContext; + }, + handlerCtx?: unknown +) => MaybePromise; + +// prettier-ignore +export type Data = + Awaited> extends Response ? never : + Awaited> + +export type Loader = DataFunction & { hydrate?: boolean }; +export type Action = DataFunction; + +export const defineLoader = ( + loader: T, + options: { hydrate?: boolean } = {} +): T => { + loader.hydrate = options.hydrate; + return loader; +}; + +export const defineAction = (action: T): T => action;