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

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
@@ -144,6 +144,23 @@ export interface LoaderFunction {
144
144
  export interface ActionFunction {
145
145
  (args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
146
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;
163
+ }
147
164
  /**
148
165
  * Route shouldRevalidate function signature. This runs after any submission
149
166
  * (navigation or fetcher), so we flatten the navigation/fetcher 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`
@@ -462,14 +466,15 @@ export declare const redirectDocument: RedirectFunction;
462
466
  * @private
463
467
  * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
464
468
  */
465
- export declare class ErrorResponse {
469
+ export declare class ErrorResponseImpl {
466
470
  status: number;
467
471
  statusText: string;
468
472
  data: any;
469
- error?: Error;
470
- internal: boolean;
473
+ private error?;
474
+ private internal;
471
475
  constructor(status: number, statusText: string | undefined, data: any, internal?: boolean);
472
476
  }
477
+ export type ErrorResponse = InstanceType<typeof ErrorResponseImpl>;
473
478
  /**
474
479
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
475
480
  * Response thrown from an action/loader
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,13 @@ export type {
23
23
  PathPattern,
24
24
  RedirectFunction,
25
25
  ShouldRevalidateFunction,
26
+ ShouldRevalidateFunctionArgs,
27
+ TrackedPromise,
26
28
  V7_FormMethod,
27
29
  } from "./utils";
28
30
 
29
31
  export {
30
32
  AbortedDeferredError,
31
- ErrorResponse,
32
33
  defer,
33
34
  generatePath,
34
35
  getToPathname,
@@ -62,9 +63,9 @@ export type {
62
63
  export {
63
64
  Action,
64
65
  createBrowserHistory,
65
- createPath,
66
66
  createHashHistory,
67
67
  createMemoryHistory,
68
+ createPath,
68
69
  parsePath,
69
70
  } from "./history";
70
71
 
@@ -81,6 +82,7 @@ export * from "./router";
81
82
  export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
82
83
  export {
83
84
  DeferredData as UNSAFE_DeferredData,
85
+ ErrorResponseImpl as UNSAFE_ErrorResponseImpl,
84
86
  convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
85
87
  getPathContributingMatches as UNSAFE_getPathContributingMatches,
86
88
  } from "./utils";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "1.8.0-pre.0",
3
+ "version": "1.9.0-pre.0",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -8,34 +8,34 @@ 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
+ AgnosticRouteMatch,
15
+ AgnosticRouteObject,
16
+ DataResult,
17
+ DeferredData,
15
18
  DeferredResult,
19
+ DetectErrorBoundaryFunction,
16
20
  ErrorResult,
17
21
  FormEncType,
18
22
  FormMethod,
19
- DetectErrorBoundaryFunction,
23
+ HTMLFormMethod,
24
+ ImmutableRouteKey,
25
+ LoaderFunction,
26
+ MapRoutePropertiesFunction,
27
+ MutationFormMethod,
20
28
  RedirectResult,
21
29
  RouteData,
22
- AgnosticRouteObject,
30
+ RouteManifest,
31
+ ShouldRevalidateFunctionArgs,
23
32
  Submission,
24
33
  SuccessResult,
25
- AgnosticRouteMatch,
26
- ShouldRevalidateFunction,
27
- RouteManifest,
28
- ImmutableRouteKey,
29
- ActionFunction,
30
- LoaderFunction,
31
- V7_MutationFormMethod,
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
40
  convertRoutesToDataRoutes,
41
41
  getPathContributingMatches,
@@ -526,7 +526,6 @@ type FetcherStates<TData = any> = {
526
526
  formData: undefined;
527
527
  json: undefined;
528
528
  data: TData | undefined;
529
- " _hasFetcherDoneAnything "?: boolean;
530
529
  };
531
530
  Loading: {
532
531
  state: "loading";
@@ -537,7 +536,6 @@ type FetcherStates<TData = any> = {
537
536
  formData: Submission["formData"] | undefined;
538
537
  json: Submission["json"] | undefined;
539
538
  data: TData | undefined;
540
- " _hasFetcherDoneAnything "?: boolean;
541
539
  };
542
540
  Submitting: {
543
541
  state: "submitting";
@@ -548,7 +546,6 @@ type FetcherStates<TData = any> = {
548
546
  formData: Submission["formData"];
549
547
  json: Submission["json"];
550
548
  data: TData | undefined;
551
- " _hasFetcherDoneAnything "?: boolean;
552
549
  };
553
550
  };
554
551
 
@@ -1229,7 +1226,7 @@ export function createRouter(init: RouterInit): Router {
1229
1226
  submission?: Submission;
1230
1227
  fetcherSubmission?: Submission;
1231
1228
  overrideNavigation?: Navigation;
1232
- pendingError?: ErrorResponse;
1229
+ pendingError?: ErrorResponseImpl;
1233
1230
  startUninterruptedRevalidation?: boolean;
1234
1231
  preventScrollReset?: boolean;
1235
1232
  replace?: boolean;
@@ -1786,8 +1783,7 @@ export function createRouter(init: RouterInit): Router {
1786
1783
  updateState({ fetchers: new Map(state.fetchers) });
1787
1784
 
1788
1785
  return startRedirectNavigation(state, actionResult, {
1789
- submission,
1790
- isFetchActionRedirect: true,
1786
+ fetcherSubmission: submission,
1791
1787
  });
1792
1788
  }
1793
1789
  }
@@ -2086,27 +2082,21 @@ export function createRouter(init: RouterInit): Router {
2086
2082
  redirect: RedirectResult,
2087
2083
  {
2088
2084
  submission,
2085
+ fetcherSubmission,
2089
2086
  replace,
2090
- isFetchActionRedirect,
2091
2087
  }: {
2092
2088
  submission?: Submission;
2089
+ fetcherSubmission?: Submission;
2093
2090
  replace?: boolean;
2094
- isFetchActionRedirect?: boolean;
2095
2091
  } = {}
2096
2092
  ) {
2097
2093
  if (redirect.revalidate) {
2098
2094
  isRevalidationRequired = true;
2099
2095
  }
2100
2096
 
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
- );
2097
+ let redirectLocation = createLocation(state.location, redirect.location, {
2098
+ _isRedirect: true,
2099
+ });
2110
2100
  invariant(
2111
2101
  redirectLocation,
2112
2102
  "Expected a location on the redirect navigation"
@@ -2146,12 +2136,21 @@ export function createRouter(init: RouterInit): Router {
2146
2136
 
2147
2137
  // Use the incoming submission if provided, fallback on the active one in
2148
2138
  // state.navigation
2149
- let activeSubmission =
2150
- submission || getSubmissionFromNavigation(state.navigation);
2139
+ let { formMethod, formAction, formEncType } = state.navigation;
2140
+ if (
2141
+ !submission &&
2142
+ !fetcherSubmission &&
2143
+ formMethod &&
2144
+ formAction &&
2145
+ formEncType
2146
+ ) {
2147
+ submission = getSubmissionFromNavigation(state.navigation);
2148
+ }
2151
2149
 
2152
2150
  // If this was a 307/308 submission we want to preserve the HTTP method and
2153
2151
  // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2154
2152
  // redirected location
2153
+ let activeSubmission = submission || fetcherSubmission;
2155
2154
  if (
2156
2155
  redirectPreserveMethodStatusCodes.has(redirect.status) &&
2157
2156
  activeSubmission &&
@@ -2165,23 +2164,17 @@ export function createRouter(init: RouterInit): Router {
2165
2164
  // Preserve this flag across redirects
2166
2165
  preventScrollReset: pendingPreventScrollReset,
2167
2166
  });
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
2167
  } else {
2178
- // If we have a submission, we will preserve it through the redirect navigation
2168
+ // If we have a navigation submission, we will preserve it through the
2169
+ // redirect navigation
2179
2170
  let overrideNavigation = getLoadingNavigation(
2180
2171
  redirectLocation,
2181
- activeSubmission
2172
+ submission
2182
2173
  );
2183
2174
  await startNavigation(redirectHistoryAction, redirectLocation, {
2184
2175
  overrideNavigation,
2176
+ // Send fetcher submissions through for shouldRevalidate
2177
+ fetcherSubmission,
2185
2178
  // Preserve this flag across redirects
2186
2179
  preventScrollReset: pendingPreventScrollReset,
2187
2180
  });
@@ -2850,7 +2843,9 @@ export function createStaticHandler(
2850
2843
 
2851
2844
  if (request.signal.aborted) {
2852
2845
  let method = isRouteRequest ? "queryRoute" : "query";
2853
- throw new Error(`${method}() call aborted`);
2846
+ throw new Error(
2847
+ `${method}() call aborted: ${request.method} ${request.url}`
2848
+ );
2854
2849
  }
2855
2850
  }
2856
2851
 
@@ -3018,7 +3013,9 @@ export function createStaticHandler(
3018
3013
 
3019
3014
  if (request.signal.aborted) {
3020
3015
  let method = isRouteRequest ? "queryRoute" : "query";
3021
- throw new Error(`${method}() call aborted`);
3016
+ throw new Error(
3017
+ `${method}() call aborted: ${request.method} ${request.url}`
3018
+ );
3022
3019
  }
3023
3020
 
3024
3021
  // Process and commit output from loaders
@@ -3172,7 +3169,7 @@ function normalizeNavigateOptions(
3172
3169
  ): {
3173
3170
  path: string;
3174
3171
  submission?: Submission;
3175
- error?: ErrorResponse;
3172
+ error?: ErrorResponseImpl;
3176
3173
  } {
3177
3174
  // Return location verbatim on non-submission navigations
3178
3175
  if (!opts || !isSubmissionNavigation(opts)) {
@@ -3513,7 +3510,7 @@ function isNewRouteInstance(
3513
3510
 
3514
3511
  function shouldRevalidateLoader(
3515
3512
  loaderMatch: AgnosticDataRouteMatch,
3516
- arg: Parameters<ShouldRevalidateFunction>[0]
3513
+ arg: ShouldRevalidateFunctionArgs
3517
3514
  ) {
3518
3515
  if (loaderMatch.route.shouldRevalidate) {
3519
3516
  let routeChoice = loaderMatch.route.shouldRevalidate(arg);
@@ -3643,10 +3640,19 @@ async function callLoaderOrAction(
3643
3640
  if (match.route.lazy) {
3644
3641
  if (handler) {
3645
3642
  // Run statically defined handler in parallel with lazy()
3643
+ let handlerError;
3646
3644
  let values = await Promise.all([
3647
- runHandler(handler),
3645
+ // If the handler throws, don't let it immediately bubble out,
3646
+ // since we need to let the lazy() execution finish so we know if this
3647
+ // route has a boundary that can handle the error
3648
+ runHandler(handler).catch((e) => {
3649
+ handlerError = e;
3650
+ }),
3648
3651
  loadLazyRouteModule(match.route, mapRouteProperties, manifest),
3649
3652
  ]);
3653
+ if (handlerError) {
3654
+ throw handlerError;
3655
+ }
3650
3656
  result = values[0];
3651
3657
  } else {
3652
3658
  // Load lazy route module, then run any returned handler
@@ -3774,7 +3780,7 @@ async function callLoaderOrAction(
3774
3780
  if (resultType === ResultType.error) {
3775
3781
  return {
3776
3782
  type: resultType,
3777
- error: new ErrorResponse(status, result.statusText, data),
3783
+ error: new ErrorResponseImpl(status, result.statusText, data),
3778
3784
  headers: result.headers,
3779
3785
  };
3780
3786
  }
@@ -4139,7 +4145,7 @@ function getInternalRouterError(
4139
4145
  }
4140
4146
  }
4141
4147
 
4142
- return new ErrorResponse(
4148
+ return new ErrorResponseImpl(
4143
4149
  status || 500,
4144
4150
  statusText,
4145
4151
  new Error(errorMessage),
@@ -4462,7 +4468,6 @@ function getLoadingFetcher(
4462
4468
  json: submission.json,
4463
4469
  text: submission.text,
4464
4470
  data,
4465
- " _hasFetcherDoneAnything ": true,
4466
4471
  };
4467
4472
  return fetcher;
4468
4473
  } else {
@@ -4475,7 +4480,6 @@ function getLoadingFetcher(
4475
4480
  json: undefined,
4476
4481
  text: undefined,
4477
4482
  data,
4478
- " _hasFetcherDoneAnything ": true,
4479
4483
  };
4480
4484
  return fetcher;
4481
4485
  }
@@ -4494,7 +4498,6 @@ function getSubmittingFetcher(
4494
4498
  json: submission.json,
4495
4499
  text: submission.text,
4496
4500
  data: existingFetcher ? existingFetcher.data : undefined,
4497
- " _hasFetcherDoneAnything ": true,
4498
4501
  };
4499
4502
  return fetcher;
4500
4503
  }
@@ -4509,7 +4512,6 @@ function getDoneFetcher(data: Fetcher["data"]): FetcherStates["Idle"] {
4509
4512
  json: undefined,
4510
4513
  text: undefined,
4511
4514
  data,
4512
- " _hasFetcherDoneAnything ": true,
4513
4515
  };
4514
4516
  return fetcher;
4515
4517
  }
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
@@ -174,6 +174,24 @@ export interface ActionFunction {
174
174
  (args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
175
175
  }
176
176
 
177
+ /**
178
+ * Arguments passed to shouldRevalidate function
179
+ */
180
+ export interface ShouldRevalidateFunctionArgs {
181
+ currentUrl: URL;
182
+ currentParams: AgnosticDataRouteMatch["params"];
183
+ nextUrl: URL;
184
+ nextParams: AgnosticDataRouteMatch["params"];
185
+ formMethod?: Submission["formMethod"];
186
+ formAction?: Submission["formAction"];
187
+ formEncType?: Submission["formEncType"];
188
+ text?: Submission["text"];
189
+ formData?: Submission["formData"];
190
+ json?: Submission["json"];
191
+ actionResult?: any;
192
+ defaultShouldRevalidate: boolean;
193
+ }
194
+
177
195
  /**
178
196
  * Route shouldRevalidate function signature. This runs after any submission
179
197
  * (navigation or fetcher), so we flatten the navigation/fetcher submission
@@ -182,20 +200,7 @@ export interface ActionFunction {
182
200
  * have to re-run based on the data models that were potentially mutated.
183
201
  */
184
202
  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;
203
+ (args: ShouldRevalidateFunctionArgs): boolean;
199
204
  }
200
205
 
201
206
  /**
@@ -1500,12 +1505,12 @@ export const redirectDocument: RedirectFunction = (url, init) => {
1500
1505
  * @private
1501
1506
  * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
1502
1507
  */
1503
- export class ErrorResponse {
1508
+ export class ErrorResponseImpl {
1504
1509
  status: number;
1505
1510
  statusText: string;
1506
1511
  data: any;
1507
- error?: Error;
1508
- internal: boolean;
1512
+ private error?: Error;
1513
+ private internal: boolean;
1509
1514
 
1510
1515
  constructor(
1511
1516
  status: number,
@@ -1525,6 +1530,11 @@ export class ErrorResponse {
1525
1530
  }
1526
1531
  }
1527
1532
 
1533
+ // We don't want the class exported since usage of it at runtime is an
1534
+ // implementation detail, but we do want to export the shape so folks can
1535
+ // build their own abstractions around instances via isRouteErrorResponse()
1536
+ export type ErrorResponse = InstanceType<typeof ErrorResponseImpl>;
1537
+
1528
1538
  /**
1529
1539
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1530
1540
  * Response thrown from an action/loader