@remix-run/router 1.8.0 → 1.9.0-pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.d.ts CHANGED
@@ -111,20 +111,20 @@ export type Submission = {
111
111
  * Arguments passed to route loader/action functions. Same for now but we keep
112
112
  * this as a private implementation detail in case they diverge in the future.
113
113
  */
114
- interface DataFunctionArgs {
114
+ interface DataFunctionArgs<Context> {
115
115
  request: Request;
116
116
  params: Params;
117
- context?: any;
117
+ context?: Context;
118
118
  }
119
119
  /**
120
120
  * Arguments passed to loader functions
121
121
  */
122
- export interface LoaderFunctionArgs extends DataFunctionArgs {
122
+ export interface LoaderFunctionArgs<C = any> extends DataFunctionArgs<C> {
123
123
  }
124
124
  /**
125
125
  * Arguments passed to action functions
126
126
  */
127
- export interface ActionFunctionArgs extends DataFunctionArgs {
127
+ export interface ActionFunctionArgs<C = any> extends DataFunctionArgs<C> {
128
128
  }
129
129
  /**
130
130
  * Loaders and actions can return anything except `undefined` (`null` is a
@@ -135,14 +135,31 @@ type DataFunctionValue = Response | NonNullable<unknown> | null;
135
135
  /**
136
136
  * Route loader function signature
137
137
  */
138
- export interface LoaderFunction {
139
- (args: LoaderFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
138
+ export interface LoaderFunction<C = any> {
139
+ (args: LoaderFunctionArgs<C>): Promise<DataFunctionValue> | DataFunctionValue;
140
140
  }
141
141
  /**
142
142
  * Route action function signature
143
143
  */
144
- export interface ActionFunction {
145
- (args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
144
+ export interface ActionFunction<C = any> {
145
+ (args: ActionFunctionArgs<C>): Promise<DataFunctionValue> | DataFunctionValue;
146
+ }
147
+ /**
148
+ * Arguments passed to shouldRevalidate function
149
+ */
150
+ export interface ShouldRevalidateFunctionArgs {
151
+ currentUrl: URL;
152
+ currentParams: AgnosticDataRouteMatch["params"];
153
+ nextUrl: URL;
154
+ nextParams: AgnosticDataRouteMatch["params"];
155
+ formMethod?: Submission["formMethod"];
156
+ formAction?: Submission["formAction"];
157
+ formEncType?: Submission["formEncType"];
158
+ text?: Submission["text"];
159
+ formData?: Submission["formData"];
160
+ json?: Submission["json"];
161
+ actionResult?: any;
162
+ defaultShouldRevalidate: boolean;
146
163
  }
147
164
  /**
148
165
  * Route shouldRevalidate function signature. This runs after any submission
@@ -152,20 +169,7 @@ export interface ActionFunction {
152
169
  * have to re-run based on the data models that were potentially mutated.
153
170
  */
154
171
  export interface ShouldRevalidateFunction {
155
- (args: {
156
- currentUrl: URL;
157
- currentParams: AgnosticDataRouteMatch["params"];
158
- nextUrl: URL;
159
- nextParams: AgnosticDataRouteMatch["params"];
160
- formMethod?: Submission["formMethod"];
161
- formAction?: Submission["formAction"];
162
- formEncType?: Submission["formEncType"];
163
- text?: Submission["text"];
164
- formData?: Submission["formData"];
165
- json?: Submission["json"];
166
- actionResult?: DataResult;
167
- defaultShouldRevalidate: boolean;
168
- }): boolean;
172
+ (args: ShouldRevalidateFunctionArgs): boolean;
169
173
  }
170
174
  /**
171
175
  * Function provided by the framework-aware layers to set `hasErrorBoundary`
@@ -297,6 +301,14 @@ export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[],
297
301
  * @see https://reactrouter.com/utils/match-routes
298
302
  */
299
303
  export declare function matchRoutes<RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject>(routes: RouteObjectType[], locationArg: Partial<Location> | string, basename?: string): AgnosticRouteMatch<string, RouteObjectType>[] | null;
304
+ export interface UIMatch<D = unknown, H = unknown> {
305
+ id: string;
306
+ pathname: string;
307
+ params: AgnosticRouteMatch["params"];
308
+ data: D;
309
+ handle: H;
310
+ }
311
+ export declare function convertRouteMatchToUiMatch(match: AgnosticDataRouteMatch, loaderData: RouteData): UIMatch;
300
312
  /**
301
313
  * Returns a path with params interpolated.
302
314
  *
@@ -462,14 +474,15 @@ export declare const redirectDocument: RedirectFunction;
462
474
  * @private
463
475
  * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
464
476
  */
465
- export declare class ErrorResponse {
477
+ export declare class ErrorResponseImpl {
466
478
  status: number;
467
479
  statusText: string;
468
480
  data: any;
469
- error?: Error;
470
- internal: boolean;
481
+ private error?;
482
+ private internal;
471
483
  constructor(status: number, statusText: string | undefined, data: any, internal?: boolean);
472
484
  }
485
+ export type ErrorResponse = InstanceType<typeof ErrorResponseImpl>;
473
486
  /**
474
487
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
475
488
  * Response thrown from an action/loader
package/history.ts CHANGED
@@ -49,15 +49,18 @@ export interface Path {
49
49
  hash: string;
50
50
  }
51
51
 
52
+ // TODO: (v7) Change the Location generic default from `any` to `unknown` and
53
+ // remove Remix `useLocation` wrapper.
54
+
52
55
  /**
53
56
  * An entry in a history stack. A location contains information about the
54
57
  * URL path, as well as possibly some arbitrary state and a key.
55
58
  */
56
- export interface Location extends Path {
59
+ export interface Location<S = any> extends Path {
57
60
  /**
58
61
  * A value of arbitrary data associated with this location.
59
62
  */
60
- state: any;
63
+ state: S;
61
64
 
62
65
  /**
63
66
  * A unique string associated with this location. May be used to safely store
package/index.ts CHANGED
@@ -9,12 +9,12 @@ export type {
9
9
  AgnosticNonIndexRouteObject,
10
10
  AgnosticRouteMatch,
11
11
  AgnosticRouteObject,
12
- LazyRouteFunction,
13
- TrackedPromise,
12
+ ErrorResponse,
14
13
  FormEncType,
15
14
  FormMethod,
16
15
  HTMLFormMethod,
17
16
  JsonFunction,
17
+ LazyRouteFunction,
18
18
  LoaderFunction,
19
19
  LoaderFunctionArgs,
20
20
  ParamParseKey,
@@ -23,12 +23,14 @@ export type {
23
23
  PathPattern,
24
24
  RedirectFunction,
25
25
  ShouldRevalidateFunction,
26
+ ShouldRevalidateFunctionArgs,
27
+ TrackedPromise,
28
+ UIMatch,
26
29
  V7_FormMethod,
27
30
  } from "./utils";
28
31
 
29
32
  export {
30
33
  AbortedDeferredError,
31
- ErrorResponse,
32
34
  defer,
33
35
  generatePath,
34
36
  getToPathname,
@@ -62,9 +64,9 @@ export type {
62
64
  export {
63
65
  Action,
64
66
  createBrowserHistory,
65
- createPath,
66
67
  createHashHistory,
67
68
  createMemoryHistory,
69
+ createPath,
68
70
  parsePath,
69
71
  } from "./history";
70
72
 
@@ -81,7 +83,9 @@ export * from "./router";
81
83
  export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
82
84
  export {
83
85
  DeferredData as UNSAFE_DeferredData,
86
+ ErrorResponseImpl as UNSAFE_ErrorResponseImpl,
84
87
  convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
88
+ convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch,
85
89
  getPathContributingMatches as UNSAFE_getPathContributingMatches,
86
90
  } from "./utils";
87
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "1.8.0",
3
+ "version": "1.9.0-pre.1",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -8,35 +8,36 @@ import {
8
8
  warning,
9
9
  } from "./history";
10
10
  import type {
11
- DataResult,
12
- DeferredData,
11
+ ActionFunction,
13
12
  AgnosticDataRouteMatch,
14
13
  AgnosticDataRouteObject,
14
+ AgnosticRouteObject,
15
+ DataResult,
16
+ DeferredData,
15
17
  DeferredResult,
18
+ DetectErrorBoundaryFunction,
16
19
  ErrorResult,
17
20
  FormEncType,
18
21
  FormMethod,
19
- DetectErrorBoundaryFunction,
22
+ HTMLFormMethod,
23
+ ImmutableRouteKey,
24
+ LoaderFunction,
25
+ MapRoutePropertiesFunction,
26
+ MutationFormMethod,
20
27
  RedirectResult,
21
28
  RouteData,
22
- AgnosticRouteObject,
29
+ RouteManifest,
30
+ ShouldRevalidateFunctionArgs,
23
31
  Submission,
24
32
  SuccessResult,
25
- AgnosticRouteMatch,
26
- ShouldRevalidateFunction,
27
- RouteManifest,
28
- ImmutableRouteKey,
29
- ActionFunction,
30
- LoaderFunction,
31
- V7_MutationFormMethod,
33
+ UIMatch,
32
34
  V7_FormMethod,
33
- HTMLFormMethod,
34
- MutationFormMethod,
35
- MapRoutePropertiesFunction,
35
+ V7_MutationFormMethod,
36
36
  } from "./utils";
37
37
  import {
38
- ErrorResponse,
38
+ ErrorResponseImpl,
39
39
  ResultType,
40
+ convertRouteMatchToUiMatch,
40
41
  convertRoutesToDataRoutes,
41
42
  getPathContributingMatches,
42
43
  immutableRouteKeys,
@@ -394,20 +395,12 @@ export interface RouterSubscriber {
394
395
  (state: RouterState): void;
395
396
  }
396
397
 
397
- interface UseMatchesMatch {
398
- id: string;
399
- pathname: string;
400
- params: AgnosticRouteMatch["params"];
401
- data: unknown;
402
- handle: unknown;
403
- }
404
-
405
398
  /**
406
399
  * Function signature for determining the key to be used in scroll restoration
407
400
  * for a given location
408
401
  */
409
402
  export interface GetScrollRestorationKeyFunction {
410
- (location: Location, matches: UseMatchesMatch[]): string | null;
403
+ (location: Location, matches: UIMatch[]): string | null;
411
404
  }
412
405
 
413
406
  /**
@@ -526,7 +519,6 @@ type FetcherStates<TData = any> = {
526
519
  formData: undefined;
527
520
  json: undefined;
528
521
  data: TData | undefined;
529
- " _hasFetcherDoneAnything "?: boolean;
530
522
  };
531
523
  Loading: {
532
524
  state: "loading";
@@ -537,7 +529,6 @@ type FetcherStates<TData = any> = {
537
529
  formData: Submission["formData"] | undefined;
538
530
  json: Submission["json"] | undefined;
539
531
  data: TData | undefined;
540
- " _hasFetcherDoneAnything "?: boolean;
541
532
  };
542
533
  Submitting: {
543
534
  state: "submitting";
@@ -548,7 +539,6 @@ type FetcherStates<TData = any> = {
548
539
  formData: Submission["formData"];
549
540
  json: Submission["json"];
550
541
  data: TData | undefined;
551
- " _hasFetcherDoneAnything "?: boolean;
552
542
  };
553
543
  };
554
544
 
@@ -1229,7 +1219,7 @@ export function createRouter(init: RouterInit): Router {
1229
1219
  submission?: Submission;
1230
1220
  fetcherSubmission?: Submission;
1231
1221
  overrideNavigation?: Navigation;
1232
- pendingError?: ErrorResponse;
1222
+ pendingError?: ErrorResponseImpl;
1233
1223
  startUninterruptedRevalidation?: boolean;
1234
1224
  preventScrollReset?: boolean;
1235
1225
  replace?: boolean;
@@ -1786,8 +1776,7 @@ export function createRouter(init: RouterInit): Router {
1786
1776
  updateState({ fetchers: new Map(state.fetchers) });
1787
1777
 
1788
1778
  return startRedirectNavigation(state, actionResult, {
1789
- submission,
1790
- isFetchActionRedirect: true,
1779
+ fetcherSubmission: submission,
1791
1780
  });
1792
1781
  }
1793
1782
  }
@@ -2086,27 +2075,21 @@ export function createRouter(init: RouterInit): Router {
2086
2075
  redirect: RedirectResult,
2087
2076
  {
2088
2077
  submission,
2078
+ fetcherSubmission,
2089
2079
  replace,
2090
- isFetchActionRedirect,
2091
2080
  }: {
2092
2081
  submission?: Submission;
2082
+ fetcherSubmission?: Submission;
2093
2083
  replace?: boolean;
2094
- isFetchActionRedirect?: boolean;
2095
2084
  } = {}
2096
2085
  ) {
2097
2086
  if (redirect.revalidate) {
2098
2087
  isRevalidationRequired = true;
2099
2088
  }
2100
2089
 
2101
- let redirectLocation = createLocation(
2102
- state.location,
2103
- redirect.location,
2104
- // TODO: This can be removed once we get rid of useTransition in Remix v2
2105
- {
2106
- _isRedirect: true,
2107
- ...(isFetchActionRedirect ? { _isFetchActionRedirect: true } : {}),
2108
- }
2109
- );
2090
+ let redirectLocation = createLocation(state.location, redirect.location, {
2091
+ _isRedirect: true,
2092
+ });
2110
2093
  invariant(
2111
2094
  redirectLocation,
2112
2095
  "Expected a location on the redirect navigation"
@@ -2146,12 +2129,21 @@ export function createRouter(init: RouterInit): Router {
2146
2129
 
2147
2130
  // Use the incoming submission if provided, fallback on the active one in
2148
2131
  // state.navigation
2149
- let activeSubmission =
2150
- submission || getSubmissionFromNavigation(state.navigation);
2132
+ let { formMethod, formAction, formEncType } = state.navigation;
2133
+ if (
2134
+ !submission &&
2135
+ !fetcherSubmission &&
2136
+ formMethod &&
2137
+ formAction &&
2138
+ formEncType
2139
+ ) {
2140
+ submission = getSubmissionFromNavigation(state.navigation);
2141
+ }
2151
2142
 
2152
2143
  // If this was a 307/308 submission we want to preserve the HTTP method and
2153
2144
  // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2154
2145
  // redirected location
2146
+ let activeSubmission = submission || fetcherSubmission;
2155
2147
  if (
2156
2148
  redirectPreserveMethodStatusCodes.has(redirect.status) &&
2157
2149
  activeSubmission &&
@@ -2165,23 +2157,17 @@ export function createRouter(init: RouterInit): Router {
2165
2157
  // Preserve this flag across redirects
2166
2158
  preventScrollReset: pendingPreventScrollReset,
2167
2159
  });
2168
- } else if (isFetchActionRedirect) {
2169
- // For a fetch action redirect, we kick off a new loading navigation
2170
- // without the fetcher submission, but we send it along for shouldRevalidate
2171
- await startNavigation(redirectHistoryAction, redirectLocation, {
2172
- overrideNavigation: getLoadingNavigation(redirectLocation),
2173
- fetcherSubmission: activeSubmission,
2174
- // Preserve this flag across redirects
2175
- preventScrollReset: pendingPreventScrollReset,
2176
- });
2177
2160
  } else {
2178
- // If we have a submission, we will preserve it through the redirect navigation
2161
+ // If we have a navigation submission, we will preserve it through the
2162
+ // redirect navigation
2179
2163
  let overrideNavigation = getLoadingNavigation(
2180
2164
  redirectLocation,
2181
- activeSubmission
2165
+ submission
2182
2166
  );
2183
2167
  await startNavigation(redirectHistoryAction, redirectLocation, {
2184
2168
  overrideNavigation,
2169
+ // Send fetcher submissions through for shouldRevalidate
2170
+ fetcherSubmission,
2185
2171
  // Preserve this flag across redirects
2186
2172
  preventScrollReset: pendingPreventScrollReset,
2187
2173
  });
@@ -2468,7 +2454,7 @@ export function createRouter(init: RouterInit): Router {
2468
2454
  if (getScrollRestorationKey) {
2469
2455
  let key = getScrollRestorationKey(
2470
2456
  location,
2471
- matches.map((m) => createUseMatchesMatch(m, state.loaderData))
2457
+ matches.map((m) => convertRouteMatchToUiMatch(m, state.loaderData))
2472
2458
  );
2473
2459
  return key || location.key;
2474
2460
  }
@@ -2850,7 +2836,9 @@ export function createStaticHandler(
2850
2836
 
2851
2837
  if (request.signal.aborted) {
2852
2838
  let method = isRouteRequest ? "queryRoute" : "query";
2853
- throw new Error(`${method}() call aborted`);
2839
+ throw new Error(
2840
+ `${method}() call aborted: ${request.method} ${request.url}`
2841
+ );
2854
2842
  }
2855
2843
  }
2856
2844
 
@@ -3018,7 +3006,9 @@ export function createStaticHandler(
3018
3006
 
3019
3007
  if (request.signal.aborted) {
3020
3008
  let method = isRouteRequest ? "queryRoute" : "query";
3021
- throw new Error(`${method}() call aborted`);
3009
+ throw new Error(
3010
+ `${method}() call aborted: ${request.method} ${request.url}`
3011
+ );
3022
3012
  }
3023
3013
 
3024
3014
  // Process and commit output from loaders
@@ -3172,7 +3162,7 @@ function normalizeNavigateOptions(
3172
3162
  ): {
3173
3163
  path: string;
3174
3164
  submission?: Submission;
3175
- error?: ErrorResponse;
3165
+ error?: ErrorResponseImpl;
3176
3166
  } {
3177
3167
  // Return location verbatim on non-submission navigations
3178
3168
  if (!opts || !isSubmissionNavigation(opts)) {
@@ -3513,7 +3503,7 @@ function isNewRouteInstance(
3513
3503
 
3514
3504
  function shouldRevalidateLoader(
3515
3505
  loaderMatch: AgnosticDataRouteMatch,
3516
- arg: Parameters<ShouldRevalidateFunction>[0]
3506
+ arg: ShouldRevalidateFunctionArgs
3517
3507
  ) {
3518
3508
  if (loaderMatch.route.shouldRevalidate) {
3519
3509
  let routeChoice = loaderMatch.route.shouldRevalidate(arg);
@@ -3643,10 +3633,19 @@ async function callLoaderOrAction(
3643
3633
  if (match.route.lazy) {
3644
3634
  if (handler) {
3645
3635
  // Run statically defined handler in parallel with lazy()
3636
+ let handlerError;
3646
3637
  let values = await Promise.all([
3647
- runHandler(handler),
3638
+ // If the handler throws, don't let it immediately bubble out,
3639
+ // since we need to let the lazy() execution finish so we know if this
3640
+ // route has a boundary that can handle the error
3641
+ runHandler(handler).catch((e) => {
3642
+ handlerError = e;
3643
+ }),
3648
3644
  loadLazyRouteModule(match.route, mapRouteProperties, manifest),
3649
3645
  ]);
3646
+ if (handlerError) {
3647
+ throw handlerError;
3648
+ }
3650
3649
  result = values[0];
3651
3650
  } else {
3652
3651
  // Load lazy route module, then run any returned handler
@@ -3774,7 +3773,7 @@ async function callLoaderOrAction(
3774
3773
  if (resultType === ResultType.error) {
3775
3774
  return {
3776
3775
  type: resultType,
3777
- error: new ErrorResponse(status, result.statusText, data),
3776
+ error: new ErrorResponseImpl(status, result.statusText, data),
3778
3777
  headers: result.headers,
3779
3778
  };
3780
3779
  }
@@ -4139,7 +4138,7 @@ function getInternalRouterError(
4139
4138
  }
4140
4139
  }
4141
4140
 
4142
- return new ErrorResponse(
4141
+ return new ErrorResponseImpl(
4143
4142
  status || 500,
4144
4143
  statusText,
4145
4144
  new Error(errorMessage),
@@ -4326,22 +4325,6 @@ function hasNakedIndexQuery(search: string): boolean {
4326
4325
  return new URLSearchParams(search).getAll("index").some((v) => v === "");
4327
4326
  }
4328
4327
 
4329
- // Note: This should match the format exported by useMatches, so if you change
4330
- // this please also change that :) Eventually we'll DRY this up
4331
- function createUseMatchesMatch(
4332
- match: AgnosticDataRouteMatch,
4333
- loaderData: RouteData
4334
- ): UseMatchesMatch {
4335
- let { route, pathname, params } = match;
4336
- return {
4337
- id: route.id,
4338
- pathname,
4339
- params,
4340
- data: loaderData[route.id] as unknown,
4341
- handle: route.handle as unknown,
4342
- };
4343
- }
4344
-
4345
4328
  function getTargetMatch(
4346
4329
  matches: AgnosticDataRouteMatch[],
4347
4330
  location: Location | string
@@ -4462,7 +4445,6 @@ function getLoadingFetcher(
4462
4445
  json: submission.json,
4463
4446
  text: submission.text,
4464
4447
  data,
4465
- " _hasFetcherDoneAnything ": true,
4466
4448
  };
4467
4449
  return fetcher;
4468
4450
  } else {
@@ -4475,7 +4457,6 @@ function getLoadingFetcher(
4475
4457
  json: undefined,
4476
4458
  text: undefined,
4477
4459
  data,
4478
- " _hasFetcherDoneAnything ": true,
4479
4460
  };
4480
4461
  return fetcher;
4481
4462
  }
@@ -4494,7 +4475,6 @@ function getSubmittingFetcher(
4494
4475
  json: submission.json,
4495
4476
  text: submission.text,
4496
4477
  data: existingFetcher ? existingFetcher.data : undefined,
4497
- " _hasFetcherDoneAnything ": true,
4498
4478
  };
4499
4479
  return fetcher;
4500
4480
  }
@@ -4509,7 +4489,6 @@ function getDoneFetcher(data: Fetcher["data"]): FetcherStates["Idle"] {
4509
4489
  json: undefined,
4510
4490
  text: undefined,
4511
4491
  data,
4512
- " _hasFetcherDoneAnything ": true,
4513
4492
  };
4514
4493
  return fetcher;
4515
4494
  }
package/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Location, Path, To } from "./history";
2
- import { warning, invariant, parsePath } from "./history";
2
+ import { invariant, parsePath, warning } from "./history";
3
3
 
4
4
  /**
5
5
  * Map of routeId -> data returned from a loader/action/error
@@ -137,21 +137,24 @@ export type Submission =
137
137
  * Arguments passed to route loader/action functions. Same for now but we keep
138
138
  * this as a private implementation detail in case they diverge in the future.
139
139
  */
140
- interface DataFunctionArgs {
140
+ interface DataFunctionArgs<Context> {
141
141
  request: Request;
142
142
  params: Params;
143
- context?: any;
143
+ context?: Context;
144
144
  }
145
145
 
146
+ // TODO: (v7) Change the defaults from any to unknown in and remove Remix wrappers:
147
+ // ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs
148
+
146
149
  /**
147
150
  * Arguments passed to loader functions
148
151
  */
149
- export interface LoaderFunctionArgs extends DataFunctionArgs {}
152
+ export interface LoaderFunctionArgs<C = any> extends DataFunctionArgs<C> {}
150
153
 
151
154
  /**
152
155
  * Arguments passed to action functions
153
156
  */
154
- export interface ActionFunctionArgs extends DataFunctionArgs {}
157
+ export interface ActionFunctionArgs<C = any> extends DataFunctionArgs<C> {}
155
158
 
156
159
  /**
157
160
  * Loaders and actions can return anything except `undefined` (`null` is a
@@ -163,15 +166,33 @@ type DataFunctionValue = Response | NonNullable<unknown> | null;
163
166
  /**
164
167
  * Route loader function signature
165
168
  */
166
- export interface LoaderFunction {
167
- (args: LoaderFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
169
+ export interface LoaderFunction<C = any> {
170
+ (args: LoaderFunctionArgs<C>): Promise<DataFunctionValue> | DataFunctionValue;
168
171
  }
169
172
 
170
173
  /**
171
174
  * Route action function signature
172
175
  */
173
- export interface ActionFunction {
174
- (args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
176
+ export interface ActionFunction<C = any> {
177
+ (args: ActionFunctionArgs<C>): Promise<DataFunctionValue> | DataFunctionValue;
178
+ }
179
+
180
+ /**
181
+ * Arguments passed to shouldRevalidate function
182
+ */
183
+ export interface ShouldRevalidateFunctionArgs {
184
+ currentUrl: URL;
185
+ currentParams: AgnosticDataRouteMatch["params"];
186
+ nextUrl: URL;
187
+ nextParams: AgnosticDataRouteMatch["params"];
188
+ formMethod?: Submission["formMethod"];
189
+ formAction?: Submission["formAction"];
190
+ formEncType?: Submission["formEncType"];
191
+ text?: Submission["text"];
192
+ formData?: Submission["formData"];
193
+ json?: Submission["json"];
194
+ actionResult?: any;
195
+ defaultShouldRevalidate: boolean;
175
196
  }
176
197
 
177
198
  /**
@@ -182,20 +203,7 @@ export interface ActionFunction {
182
203
  * have to re-run based on the data models that were potentially mutated.
183
204
  */
184
205
  export interface ShouldRevalidateFunction {
185
- (args: {
186
- currentUrl: URL;
187
- currentParams: AgnosticDataRouteMatch["params"];
188
- nextUrl: URL;
189
- nextParams: AgnosticDataRouteMatch["params"];
190
- formMethod?: Submission["formMethod"];
191
- formAction?: Submission["formAction"];
192
- formEncType?: Submission["formEncType"];
193
- text?: Submission["text"];
194
- formData?: Submission["formData"];
195
- json?: Submission["json"];
196
- actionResult?: DataResult;
197
- defaultShouldRevalidate: boolean;
198
- }): boolean;
206
+ (args: ShouldRevalidateFunctionArgs): boolean;
199
207
  }
200
208
 
201
209
  /**
@@ -485,6 +493,28 @@ export function matchRoutes<
485
493
  return matches;
486
494
  }
487
495
 
496
+ export interface UIMatch<D = unknown, H = unknown> {
497
+ id: string;
498
+ pathname: string;
499
+ params: AgnosticRouteMatch["params"];
500
+ data: D;
501
+ handle: H;
502
+ }
503
+
504
+ export function convertRouteMatchToUiMatch(
505
+ match: AgnosticDataRouteMatch,
506
+ loaderData: RouteData
507
+ ): UIMatch {
508
+ let { route, pathname, params } = match;
509
+ return {
510
+ id: route.id,
511
+ pathname,
512
+ params,
513
+ data: loaderData[route.id],
514
+ handle: route.handle,
515
+ };
516
+ }
517
+
488
518
  interface RouteMeta<
489
519
  RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject
490
520
  > {
@@ -1500,12 +1530,12 @@ export const redirectDocument: RedirectFunction = (url, init) => {
1500
1530
  * @private
1501
1531
  * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
1502
1532
  */
1503
- export class ErrorResponse {
1533
+ export class ErrorResponseImpl {
1504
1534
  status: number;
1505
1535
  statusText: string;
1506
1536
  data: any;
1507
- error?: Error;
1508
- internal: boolean;
1537
+ private error?: Error;
1538
+ private internal: boolean;
1509
1539
 
1510
1540
  constructor(
1511
1541
  status: number,
@@ -1525,6 +1555,11 @@ export class ErrorResponse {
1525
1555
  }
1526
1556
  }
1527
1557
 
1558
+ // We don't want the class exported since usage of it at runtime is an
1559
+ // implementation detail, but we do want to export the shape so folks can
1560
+ // build their own abstractions around instances via isRouteErrorResponse()
1561
+ export type ErrorResponse = InstanceType<typeof ErrorResponseImpl>;
1562
+
1528
1563
  /**
1529
1564
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1530
1565
  * Response thrown from an action/loader