-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
Add generics for Remix type enhancements #10843
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| "react-router-dom": patch | ||
| "react-router": patch | ||
| "@remix-run/router": patch | ||
| --- | ||
|
|
||
| In order to move towards stricter TypeScript support in the future, we're aiming to replace current usages of `any` with `unknown` on exposed typings for user-provided data. To do this in Remix v2 without introducing breaking changes in React Router v6, we have added generics to a number of shared types. These continue to default to `any` in React Router and are overridden with `unknown` in Remix. In React Router v7 we plan to move these to `unknown` as a breakjing change. | ||
|
|
||
| - `Location` now accepts a generic for the `location.state` value | ||
| - `ActionFunctionArgs`/`ActionFunction`/`LoaderFunctionArgs`/`LoaderFunction` now accept a generic for the `context` parameter (only used in SSR usages via `createStaticHandler`) | ||
| - The return type of `useMatches` (now exported as `UIMatch`) accepts generics for `match.data` and `match.handle` - both of which were already set to `unknown` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,6 @@ import type { | |
| ActionFunction, | ||
| AgnosticDataRouteMatch, | ||
| AgnosticDataRouteObject, | ||
| AgnosticRouteMatch, | ||
| AgnosticRouteObject, | ||
| DataResult, | ||
| DeferredData, | ||
|
|
@@ -31,12 +30,14 @@ import type { | |
| ShouldRevalidateFunctionArgs, | ||
| Submission, | ||
| SuccessResult, | ||
| UIMatch, | ||
| V7_FormMethod, | ||
| V7_MutationFormMethod, | ||
| } from "./utils"; | ||
| import { | ||
| ErrorResponseImpl, | ||
| ResultType, | ||
| convertRouteMatchToUiMatch, | ||
| convertRoutesToDataRoutes, | ||
| getPathContributingMatches, | ||
| immutableRouteKeys, | ||
|
|
@@ -394,20 +395,12 @@ export interface RouterSubscriber { | |
| (state: RouterState): void; | ||
| } | ||
|
|
||
| interface UseMatchesMatch { | ||
| id: string; | ||
| pathname: string; | ||
| params: AgnosticRouteMatch["params"]; | ||
| data: unknown; | ||
| handle: unknown; | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now exported from |
||
|
|
||
| /** | ||
| * Function signature for determining the key to be used in scroll restoration | ||
| * for a given location | ||
| */ | ||
| export interface GetScrollRestorationKeyFunction { | ||
| (location: Location, matches: UseMatchesMatch[]): string | null; | ||
| (location: Location, matches: UIMatch[]): string | null; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -2461,7 +2454,7 @@ export function createRouter(init: RouterInit): Router { | |
| if (getScrollRestorationKey) { | ||
| let key = getScrollRestorationKey( | ||
| location, | ||
| matches.map((m) => createUseMatchesMatch(m, state.loaderData)) | ||
| matches.map((m) => convertRouteMatchToUiMatch(m, state.loaderData)) | ||
| ); | ||
| return key || location.key; | ||
| } | ||
|
|
@@ -4332,22 +4325,6 @@ function hasNakedIndexQuery(search: string): boolean { | |
| return new URLSearchParams(search).getAll("index").some((v) => v === ""); | ||
| } | ||
|
|
||
| // Note: This should match the format exported by useMatches, so if you change | ||
| // this please also change that :) Eventually we'll DRY this up | ||
| function createUseMatchesMatch( | ||
| match: AgnosticDataRouteMatch, | ||
| loaderData: RouteData | ||
| ): UseMatchesMatch { | ||
| let { route, pathname, params } = match; | ||
| return { | ||
| id: route.id, | ||
| pathname, | ||
| params, | ||
| data: loaderData[route.id] as unknown, | ||
| handle: route.handle as unknown, | ||
| }; | ||
| } | ||
|
|
||
| function getTargetMatch( | ||
| matches: AgnosticDataRouteMatch[], | ||
| location: Location | string | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -137,21 +137,24 @@ export type Submission = | |||||||||
| * Arguments passed to route loader/action functions. Same for now but we keep | ||||||||||
| * this as a private implementation detail in case they diverge in the future. | ||||||||||
| */ | ||||||||||
| interface DataFunctionArgs { | ||||||||||
| interface DataFunctionArgs<Context> { | ||||||||||
| request: Request; | ||||||||||
| params: Params; | ||||||||||
| context?: any; | ||||||||||
| context?: Context; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // TODO: (v7) Change the defaults from any to unknown in and remove Remix wrappers: | ||||||||||
| // ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Arguments passed to loader functions | ||||||||||
| */ | ||||||||||
| export interface LoaderFunctionArgs extends DataFunctionArgs {} | ||||||||||
| export interface LoaderFunctionArgs<C = any> extends DataFunctionArgs<C> {} | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Arguments passed to action functions | ||||||||||
| */ | ||||||||||
| export interface ActionFunctionArgs extends DataFunctionArgs {} | ||||||||||
| export interface ActionFunctionArgs<C = any> extends DataFunctionArgs<C> {} | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Loaders and actions can return anything except `undefined` (`null` is a | ||||||||||
|
|
@@ -163,15 +166,15 @@ type DataFunctionValue = Response | NonNullable<unknown> | null; | |||||||||
| /** | ||||||||||
| * Route loader function signature | ||||||||||
| */ | ||||||||||
| export interface LoaderFunction { | ||||||||||
| (args: LoaderFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue; | ||||||||||
| export interface LoaderFunction<C = any> { | ||||||||||
| (args: LoaderFunctionArgs<C>): Promise<DataFunctionValue> | DataFunctionValue; | ||||||||||
|
Comment on lines
+169
to
+170
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Route action function signature | ||||||||||
| */ | ||||||||||
| export interface ActionFunction { | ||||||||||
| (args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue; | ||||||||||
| export interface ActionFunction<C = any> { | ||||||||||
| (args: ActionFunctionArgs<C>): Promise<DataFunctionValue> | DataFunctionValue; | ||||||||||
|
Comment on lines
+176
to
+177
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -490,6 +493,28 @@ export function matchRoutes< | |||||||||
| return matches; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| export interface UIMatch<D = unknown, H = unknown> { | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| id: string; | ||||||||||
| pathname: string; | ||||||||||
| params: AgnosticRouteMatch["params"]; | ||||||||||
| data: D; | ||||||||||
| handle: H; | ||||||||||
|
Comment on lines
+500
to
+501
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| } | ||||||||||
|
|
||||||||||
| export function convertRouteMatchToUiMatch( | ||||||||||
| match: AgnosticDataRouteMatch, | ||||||||||
| loaderData: RouteData | ||||||||||
| ): UIMatch { | ||||||||||
| let { route, pathname, params } = match; | ||||||||||
| return { | ||||||||||
| id: route.id, | ||||||||||
| pathname, | ||||||||||
| params, | ||||||||||
| data: loaderData[route.id], | ||||||||||
| handle: route.handle, | ||||||||||
| }; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface RouteMeta< | ||||||||||
| RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject | ||||||||||
| > { | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.