@remix-run/router 1.3.0 → 1.3.1-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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.3.1-pre.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Respect `preventScrollReset` on `fetcher.Form` ([#9963](https://github.com/remix-run/react-router/pull/9963))
8
+ - Fix revalidating fetcher `shouldRevalidate` params ([#9948](https://github.com/remix-run/react-router/pull/9948))
9
+ - Do not short circuit on hash change only mutation submissions ([#9944](https://github.com/remix-run/react-router/pull/9944))
10
+ - Remove `instanceof` check from `isRouteErrorResponse` to avoid bundling issues on the server ([#9930](https://github.com/remix-run/react-router/pull/9930))
11
+ - Detect no lazy data and remove abort controller for `defer` ([#9965](https://github.com/remix-run/react-router/pull/9965))
12
+ - Send the name as the value when url-encoding `File` `FormData` entries ([#9867](https://github.com/remix-run/react-router/pull/9867))
13
+
3
14
  ## 1.3.0
4
15
 
5
16
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.3.0
2
+ * @remix-run/router v1.3.1-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -449,10 +449,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
449
449
  });
450
450
  }
451
451
  } else {
452
- warning$1(false, // TODO: Write up a doc that explains our blocking strategy in detail
453
- // and link to it here so people can understand better what is going on
454
- // and how to avoid it.
455
- "You are trying to block a POP navigation to a location that was not " + "created by @remix-run/router. The block will fail silently in " + "production, but in general you should do all navigation with the " + "router (instead of using window.history.pushState directly) " + "to avoid this situation.");
452
+ warning$1(false, "You are trying to perform a POP navigation to a location that was not " + "created by @remix-run/router. This will fail silently in production. " + "You should navigate via the router to avoid this situation (instead of " + "using window.history.pushState/window.location.hash).");
456
453
  }
457
454
  }
458
455
 
@@ -1250,6 +1247,12 @@ class DeferredData {
1250
1247
  [key]: this.trackPromise(key, value)
1251
1248
  });
1252
1249
  }, {});
1250
+
1251
+ if (this.done) {
1252
+ // All incoming values were resolved
1253
+ this.unlistenAbortSignal();
1254
+ }
1255
+
1253
1256
  this.init = responseInit;
1254
1257
  }
1255
1258
 
@@ -1437,11 +1440,11 @@ class ErrorResponse {
1437
1440
  }
1438
1441
  /**
1439
1442
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1440
- * Response throw from an action/loader
1443
+ * Response thrown from an action/loader
1441
1444
  */
1442
1445
 
1443
- function isRouteErrorResponse(e) {
1444
- return e instanceof ErrorResponse;
1446
+ function isRouteErrorResponse(error) {
1447
+ return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
1445
1448
  }
1446
1449
 
1447
1450
  //#region Types and Constants
@@ -1905,10 +1908,12 @@ function createRouter(init) {
1905
1908
  }
1906
1909
  });
1907
1910
  return;
1908
- } // Short circuit if it's only a hash change
1911
+ } // Short circuit if it's only a hash change and not a mutation submission
1912
+ // For example, on /page#hash and submit a <Form method="post"> which will
1913
+ // default to a navigation to /page
1909
1914
 
1910
1915
 
1911
- if (isHashChangeOnly(state.location, location)) {
1916
+ if (isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
1912
1917
  completeNavigation(location, {
1913
1918
  matches
1914
1919
  });
@@ -2122,9 +2127,8 @@ function createRouter(init) {
2122
2127
 
2123
2128
 
2124
2129
  if (!isUninterruptedRevalidation) {
2125
- revalidatingFetchers.forEach(_ref2 => {
2126
- let [key] = _ref2;
2127
- let fetcher = state.fetchers.get(key);
2130
+ revalidatingFetchers.forEach(rf => {
2131
+ let fetcher = state.fetchers.get(rf.key);
2128
2132
  let revalidatingFetcher = {
2129
2133
  state: "loading",
2130
2134
  data: fetcher && fetcher.data,
@@ -2134,7 +2138,7 @@ function createRouter(init) {
2134
2138
  formData: undefined,
2135
2139
  " _hasFetcherDoneAnything ": true
2136
2140
  };
2137
- state.fetchers.set(key, revalidatingFetcher);
2141
+ state.fetchers.set(rf.key, revalidatingFetcher);
2138
2142
  });
2139
2143
  let actionData = pendingActionData || state.actionData;
2140
2144
  updateState(_extends({
@@ -2149,10 +2153,7 @@ function createRouter(init) {
2149
2153
  }
2150
2154
 
2151
2155
  pendingNavigationLoadId = ++incrementingLoadId;
2152
- revalidatingFetchers.forEach(_ref3 => {
2153
- let [key] = _ref3;
2154
- return fetchControllers.set(key, pendingNavigationController);
2155
- });
2156
+ revalidatingFetchers.forEach(rf => fetchControllers.set(rf.key, pendingNavigationController));
2156
2157
  let {
2157
2158
  results,
2158
2159
  loaderResults,
@@ -2168,10 +2169,7 @@ function createRouter(init) {
2168
2169
  // reassigned to new controllers for the next navigation
2169
2170
 
2170
2171
 
2171
- revalidatingFetchers.forEach(_ref4 => {
2172
- let [key] = _ref4;
2173
- return fetchControllers.delete(key);
2174
- }); // If any loaders returned a redirect Response, start a new REPLACE navigation
2172
+ revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
2175
2173
 
2176
2174
  let redirect = findRedirect(results);
2177
2175
 
@@ -2235,6 +2233,7 @@ function createRouter(init) {
2235
2233
  submission
2236
2234
  } = normalizeNavigateOptions(href, opts, true);
2237
2235
  let match = getTargetMatch(matches, path);
2236
+ pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2238
2237
 
2239
2238
  if (submission && isMutationMethod(submission.formMethod)) {
2240
2239
  handleFetcherAction(key, routeId, path, match, matches, submission);
@@ -2243,7 +2242,12 @@ function createRouter(init) {
2243
2242
  // revalidations
2244
2243
 
2245
2244
 
2246
- fetchLoadMatches.set(key, [path, match, matches]);
2245
+ fetchLoadMatches.set(key, {
2246
+ routeId,
2247
+ path,
2248
+ match,
2249
+ matches
2250
+ });
2247
2251
  handleFetcherLoader(key, routeId, path, match, matches, submission);
2248
2252
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
2249
2253
  // errors, and revalidation
@@ -2349,11 +2353,8 @@ function createRouter(init) {
2349
2353
  // current fetcher which we want to keep in it's current loading state which
2350
2354
  // contains it's action submission info + action data
2351
2355
 
2352
- revalidatingFetchers.filter(_ref5 => {
2353
- let [staleKey] = _ref5;
2354
- return staleKey !== key;
2355
- }).forEach(_ref6 => {
2356
- let [staleKey] = _ref6;
2356
+ revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
2357
+ let staleKey = rf.key;
2357
2358
  let existingFetcher = state.fetchers.get(staleKey);
2358
2359
  let revalidatingFetcher = {
2359
2360
  state: "loading",
@@ -2382,10 +2383,7 @@ function createRouter(init) {
2382
2383
 
2383
2384
  fetchReloadIds.delete(key);
2384
2385
  fetchControllers.delete(key);
2385
- revalidatingFetchers.forEach(_ref7 => {
2386
- let [staleKey] = _ref7;
2387
- return fetchControllers.delete(staleKey);
2388
- });
2386
+ revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2389
2387
  let redirect = findRedirect(results);
2390
2388
 
2391
2389
  if (redirect) {
@@ -2625,16 +2623,10 @@ function createRouter(init) {
2625
2623
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2626
2624
  // then slice off the results into separate arrays so we can handle them
2627
2625
  // accordingly
2628
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(_ref8 => {
2629
- let [, href, match, fetchMatches] = _ref8;
2630
- return callLoaderOrAction("loader", createClientSideRequest(init.history, href, request.signal), match, fetchMatches, router.basename);
2631
- })]);
2626
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename))]);
2632
2627
  let loaderResults = results.slice(0, matchesToLoad.length);
2633
2628
  let fetcherResults = results.slice(matchesToLoad.length);
2634
- await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(_ref9 => {
2635
- let [,, match] = _ref9;
2636
- return match;
2637
- }), fetcherResults, request.signal, true)]);
2629
+ await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, request.signal, true)]);
2638
2630
  return {
2639
2631
  results,
2640
2632
  loaderResults,
@@ -2773,12 +2765,12 @@ function createRouter(init) {
2773
2765
  });
2774
2766
  }
2775
2767
 
2776
- function shouldBlockNavigation(_ref10) {
2768
+ function shouldBlockNavigation(_ref2) {
2777
2769
  let {
2778
2770
  currentLocation,
2779
2771
  nextLocation,
2780
2772
  historyAction
2781
- } = _ref10;
2773
+ } = _ref2;
2782
2774
 
2783
2775
  if (activeBlocker == null) {
2784
2776
  return;
@@ -3372,24 +3364,15 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
3372
3364
 
3373
3365
 
3374
3366
  let parsedPath = parsePath(path);
3367
+ let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
3368
+ // navigation GET submissions which run all loaders), we need to preserve
3369
+ // any incoming ?index params
3375
3370
 
3376
- try {
3377
- let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
3378
- // navigation GET submissions which run all loaders), we need to preserve
3379
- // any incoming ?index params
3380
-
3381
- if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3382
- searchParams.append("index", "");
3383
- }
3384
-
3385
- parsedPath.search = "?" + searchParams;
3386
- } catch (e) {
3387
- return {
3388
- path,
3389
- error: getInternalRouterError(400)
3390
- };
3371
+ if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3372
+ searchParams.append("index", "");
3391
3373
  }
3392
3374
 
3375
+ parsedPath.search = "?" + searchParams;
3393
3376
  return {
3394
3377
  path: createPath(parsedPath),
3395
3378
  submission
@@ -3413,25 +3396,73 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3413
3396
  }
3414
3397
 
3415
3398
  function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError, fetchLoadMatches) {
3416
- let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined; // Pick navigation matches that are net-new or qualify for revalidation
3399
+ let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3400
+ let currentUrl = history.createURL(state.location);
3401
+ let nextUrl = history.createURL(location);
3402
+ let defaultShouldRevalidate = // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
3403
+ isRevalidationRequired || // Clicked the same link, resubmitted a GET form
3404
+ currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
3405
+ currentUrl.search !== nextUrl.search; // Pick navigation matches that are net-new or qualify for revalidation
3417
3406
 
3418
3407
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3419
3408
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3420
- let navigationMatches = boundaryMatches.filter((match, index) => match.route.loader != null && (isNewLoader(state.loaderData, state.matches[index], match) || // If this route had a pending deferred cancelled it must be revalidated
3421
- cancelledDeferredRoutes.some(id => id === match.route.id) || shouldRevalidateLoader(history, state.location, state.matches[index], submission, location, match, isRevalidationRequired, actionResult))); // Pick fetcher.loads that need to be revalidated
3409
+ let navigationMatches = boundaryMatches.filter((match, index) => {
3410
+ if (match.route.loader == null) {
3411
+ return false;
3412
+ } // Always call the loader on new route instances and pending defer cancellations
3422
3413
 
3423
- let revalidatingFetchers = [];
3424
- fetchLoadMatches && fetchLoadMatches.forEach((_ref11, key) => {
3425
- let [href, match, fetchMatches] = _ref11;
3426
3414
 
3427
- // This fetcher was cancelled from a prior action submission - force reload
3428
- if (cancelledFetcherLoads.includes(key)) {
3429
- revalidatingFetchers.push([key, href, match, fetchMatches]);
3430
- } else if (isRevalidationRequired) {
3431
- let shouldRevalidate = shouldRevalidateLoader(history, href, match, submission, href, match, isRevalidationRequired, actionResult);
3415
+ if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
3416
+ return true;
3417
+ } // This is the default implementation for when we revalidate. If the route
3418
+ // provides it's own implementation, then we give them full control but
3419
+ // provide this value so they can leverage it if needed after they check
3420
+ // their own specific use cases
3421
+
3422
+
3423
+ let currentRouteMatch = state.matches[index];
3424
+ let nextRouteMatch = match;
3425
+ return shouldRevalidateLoader(match, _extends({
3426
+ currentUrl,
3427
+ currentParams: currentRouteMatch.params,
3428
+ nextUrl,
3429
+ nextParams: nextRouteMatch.params
3430
+ }, submission, {
3431
+ actionResult,
3432
+ defaultShouldRevalidate: defaultShouldRevalidate || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
3433
+ }));
3434
+ }); // Pick fetcher.loads that need to be revalidated
3435
+
3436
+ let revalidatingFetchers = [];
3437
+ fetchLoadMatches && fetchLoadMatches.forEach((f, key) => {
3438
+ if (!matches.some(m => m.route.id === f.routeId)) {
3439
+ // This fetcher is not going to be present in the subsequent render so
3440
+ // there's no need to revalidate it
3441
+ return;
3442
+ } else if (cancelledFetcherLoads.includes(key)) {
3443
+ // This fetcher was cancelled from a prior action submission - force reload
3444
+ revalidatingFetchers.push(_extends({
3445
+ key
3446
+ }, f));
3447
+ } else {
3448
+ // Revalidating fetchers are decoupled from the route matches since they
3449
+ // hit a static href, so they _always_ check shouldRevalidate and the
3450
+ // default is strictly if a revalidation is explicitly required (action
3451
+ // submissions, useRevalidator, X-Remix-Revalidate).
3452
+ let shouldRevalidate = shouldRevalidateLoader(f.match, _extends({
3453
+ currentUrl,
3454
+ currentParams: state.matches[state.matches.length - 1].params,
3455
+ nextUrl,
3456
+ nextParams: matches[matches.length - 1].params
3457
+ }, submission, {
3458
+ actionResult,
3459
+ defaultShouldRevalidate
3460
+ }));
3432
3461
 
3433
3462
  if (shouldRevalidate) {
3434
- revalidatingFetchers.push([key, href, match, fetchMatches]);
3463
+ revalidatingFetchers.push(_extends({
3464
+ key
3465
+ }, f));
3435
3466
  }
3436
3467
  }
3437
3468
  });
@@ -3454,43 +3485,20 @@ function isNewRouteInstance(currentMatch, match) {
3454
3485
  return (// param change for this match, /users/123 -> /users/456
3455
3486
  currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
3456
3487
  // e.g. /files/images/avatar.jpg -> files/finances.xls
3457
- currentPath && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3488
+ currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3458
3489
  );
3459
3490
  }
3460
3491
 
3461
- function shouldRevalidateLoader(history, currentLocation, currentMatch, submission, location, match, isRevalidationRequired, actionResult) {
3462
- let currentUrl = history.createURL(currentLocation);
3463
- let currentParams = currentMatch.params;
3464
- let nextUrl = history.createURL(location);
3465
- let nextParams = match.params; // This is the default implementation as to when we revalidate. If the route
3466
- // provides it's own implementation, then we give them full control but
3467
- // provide this value so they can leverage it if needed after they check
3468
- // their own specific use cases
3469
- // Note that fetchers always provide the same current/next locations so the
3470
- // URL-based checks here don't apply to fetcher shouldRevalidate calls
3471
-
3472
- let defaultShouldRevalidate = isNewRouteInstance(currentMatch, match) || // Clicked the same link, resubmitted a GET form
3473
- currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
3474
- currentUrl.search !== nextUrl.search || // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
3475
- isRevalidationRequired;
3476
-
3477
- if (match.route.shouldRevalidate) {
3478
- let routeChoice = match.route.shouldRevalidate(_extends({
3479
- currentUrl,
3480
- currentParams,
3481
- nextUrl,
3482
- nextParams
3483
- }, submission, {
3484
- actionResult,
3485
- defaultShouldRevalidate
3486
- }));
3492
+ function shouldRevalidateLoader(loaderMatch, arg) {
3493
+ if (loaderMatch.route.shouldRevalidate) {
3494
+ let routeChoice = loaderMatch.route.shouldRevalidate(arg);
3487
3495
 
3488
3496
  if (typeof routeChoice === "boolean") {
3489
3497
  return routeChoice;
3490
3498
  }
3491
3499
  }
3492
3500
 
3493
- return defaultShouldRevalidate;
3501
+ return arg.defaultShouldRevalidate;
3494
3502
  }
3495
3503
 
3496
3504
  async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
@@ -3665,8 +3673,8 @@ function convertFormDataToSearchParams(formData) {
3665
3673
  let searchParams = new URLSearchParams();
3666
3674
 
3667
3675
  for (let [key, value] of formData.entries()) {
3668
- invariant(typeof value === "string", 'File inputs are not supported with encType "application/x-www-form-urlencoded", ' + 'please use "multipart/form-data" instead.');
3669
- searchParams.append(key, value);
3676
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
3677
+ searchParams.append(key, value instanceof File ? value.name : value);
3670
3678
  }
3671
3679
 
3672
3680
  return searchParams;
@@ -3757,7 +3765,10 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingError,
3757
3765
  } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds); // Process results from our revalidating fetchers
3758
3766
 
3759
3767
  for (let index = 0; index < revalidatingFetchers.length; index++) {
3760
- let [key,, match] = revalidatingFetchers[index];
3768
+ let {
3769
+ key,
3770
+ match
3771
+ } = revalidatingFetchers[index];
3761
3772
  invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
3762
3773
  let result = fetcherResults[index]; // Process fetcher non-redirect errors
3763
3774
 
@@ -3863,8 +3874,6 @@ function getInternalRouterError(status, _temp4) {
3863
3874
  errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
3864
3875
  } else if (type === "defer-action") {
3865
3876
  errorMessage = "defer() is not supported in actions";
3866
- } else {
3867
- errorMessage = "Cannot submit binary form data using GET";
3868
3877
  }
3869
3878
  } else if (status === 403) {
3870
3879
  statusText = "Forbidden";