Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type inference #11460

Draft
wants to merge 1 commit into
base: v7
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/react-router/lib/hooks.tsx
Expand Up @@ -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
Expand Down Expand Up @@ -925,15 +926,15 @@ export function useMatches(): UIMatch[] {
/**
* Returns the loader data for the nearest ancestor Route loader
*/
export function useLoaderData(): unknown {
export function useLoaderData<T extends Loader>(): Data<T> {
let state = useDataRouterState(DataRouterStateHook.UseLoaderData);
let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData);

if (state.errors && state.errors[routeId] != null) {
console.error(
`You cannot \`useLoaderData\` in an errorElement (routeId: ${routeId})`
);
return undefined;
return undefined as any;
}
return state.loaderData[routeId];
}
Expand All @@ -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<T extends Action>(): Data<T> {
let state = useDataRouterState(DataRouterStateHook.UseActionData);
let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData);
return state.actionData ? state.actionData[routeId] : undefined;
Expand Down
53 changes: 53 additions & 0 deletions packages/react-router/lib/serialize.ts
@@ -0,0 +1,53 @@
type Serializable =
| undefined
| null
| boolean
| string
| symbol
| number
| Array<Serializable>
| { [key: PropertyKey]: Serializable }
| bigint
| Date
| URL
| RegExp
| Error
| Map<Serializable, Serializable>
| Set<Serializable>
| Promise<Serializable>;

export type Params<Key extends string = string> = {
readonly [key in Key]: string | undefined;
};

// TODO: rename to `Context` or `DataContext` or `AppContext`?
interface AppLoadContext {}

type MaybePromise<T> = T | Promise<T>;

type DataFunction = (
args: {
request: Request;
params: Params;
context?: AppLoadContext;
},
handlerCtx?: unknown
) => MaybePromise<Response | Serializable | null>;

// prettier-ignore
export type Data<T extends DataFunction> =
Awaited<ReturnType<T>> extends Response ? never :
Awaited<ReturnType<T>>

export type Loader = DataFunction & { hydrate?: boolean };
export type Action = DataFunction;

export const defineLoader = <T extends Loader>(
loader: T,
options: { hydrate?: boolean } = {}
): T => {
loader.hydrate = options.hydrate;
return loader;
};

export const defineAction = <T extends Action>(action: T): T => action;