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