@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.
package/dist/router.js CHANGED
@@ -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
  *
@@ -402,26 +402,17 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
402
402
  }
403
403
 
404
404
  function handlePop() {
405
- let nextAction = Action.Pop;
405
+ action = Action.Pop;
406
406
  let nextIndex = getIndex();
407
+ let delta = nextIndex == null ? null : nextIndex - index;
408
+ index = nextIndex;
407
409
 
408
- if (nextIndex != null) {
409
- let delta = nextIndex - index;
410
- action = nextAction;
411
- index = nextIndex;
412
-
413
- if (listener) {
414
- listener({
415
- action,
416
- location: history.location,
417
- delta
418
- });
419
- }
420
- } else {
421
- warning$1(false, // TODO: Write up a doc that explains our blocking strategy in detail
422
- // and link to it here so people can understand better what is going on
423
- // and how to avoid it.
424
- "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.");
410
+ if (listener) {
411
+ listener({
412
+ action,
413
+ location: history.location,
414
+ delta
415
+ });
425
416
  }
426
417
  }
427
418
 
@@ -1209,6 +1200,12 @@ class DeferredData {
1209
1200
  [key]: this.trackPromise(key, value)
1210
1201
  });
1211
1202
  }, {});
1203
+
1204
+ if (this.done) {
1205
+ // All incoming values were resolved
1206
+ this.unlistenAbortSignal();
1207
+ }
1208
+
1212
1209
  this.init = responseInit;
1213
1210
  }
1214
1211
 
@@ -1396,11 +1393,11 @@ class ErrorResponse {
1396
1393
  }
1397
1394
  /**
1398
1395
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1399
- * Response throw from an action/loader
1396
+ * Response thrown from an action/loader
1400
1397
  */
1401
1398
 
1402
- function isRouteErrorResponse(e) {
1403
- return e instanceof ErrorResponse;
1399
+ function isRouteErrorResponse(error) {
1400
+ return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
1404
1401
  }
1405
1402
 
1406
1403
  const validMutationMethodsArr = ["post", "put", "patch", "delete"];
@@ -1568,13 +1565,14 @@ function createRouter(init) {
1568
1565
  return;
1569
1566
  }
1570
1567
 
1568
+ 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.");
1571
1569
  let blockerKey = shouldBlockNavigation({
1572
1570
  currentLocation: state.location,
1573
1571
  nextLocation: location,
1574
1572
  historyAction
1575
1573
  });
1576
1574
 
1577
- if (blockerKey) {
1575
+ if (blockerKey && delta != null) {
1578
1576
  // Restore the URL to match the current UI, but don't update router state
1579
1577
  ignoreNextHistoryUpdate = true;
1580
1578
  init.history.go(delta * -1); // Put the blocker into a blocked state
@@ -1856,10 +1854,12 @@ function createRouter(init) {
1856
1854
  }
1857
1855
  });
1858
1856
  return;
1859
- } // Short circuit if it's only a hash change
1857
+ } // Short circuit if it's only a hash change and not a mutation submission
1858
+ // For example, on /page#hash and submit a <Form method="post"> which will
1859
+ // default to a navigation to /page
1860
1860
 
1861
1861
 
1862
- if (isHashChangeOnly(state.location, location)) {
1862
+ if (isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
1863
1863
  completeNavigation(location, {
1864
1864
  matches
1865
1865
  });
@@ -2073,9 +2073,8 @@ function createRouter(init) {
2073
2073
 
2074
2074
 
2075
2075
  if (!isUninterruptedRevalidation) {
2076
- revalidatingFetchers.forEach(_ref2 => {
2077
- let [key] = _ref2;
2078
- let fetcher = state.fetchers.get(key);
2076
+ revalidatingFetchers.forEach(rf => {
2077
+ let fetcher = state.fetchers.get(rf.key);
2079
2078
  let revalidatingFetcher = {
2080
2079
  state: "loading",
2081
2080
  data: fetcher && fetcher.data,
@@ -2085,7 +2084,7 @@ function createRouter(init) {
2085
2084
  formData: undefined,
2086
2085
  " _hasFetcherDoneAnything ": true
2087
2086
  };
2088
- state.fetchers.set(key, revalidatingFetcher);
2087
+ state.fetchers.set(rf.key, revalidatingFetcher);
2089
2088
  });
2090
2089
  let actionData = pendingActionData || state.actionData;
2091
2090
  updateState(_extends({
@@ -2100,10 +2099,7 @@ function createRouter(init) {
2100
2099
  }
2101
2100
 
2102
2101
  pendingNavigationLoadId = ++incrementingLoadId;
2103
- revalidatingFetchers.forEach(_ref3 => {
2104
- let [key] = _ref3;
2105
- return fetchControllers.set(key, pendingNavigationController);
2106
- });
2102
+ revalidatingFetchers.forEach(rf => fetchControllers.set(rf.key, pendingNavigationController));
2107
2103
  let {
2108
2104
  results,
2109
2105
  loaderResults,
@@ -2119,10 +2115,7 @@ function createRouter(init) {
2119
2115
  // reassigned to new controllers for the next navigation
2120
2116
 
2121
2117
 
2122
- revalidatingFetchers.forEach(_ref4 => {
2123
- let [key] = _ref4;
2124
- return fetchControllers.delete(key);
2125
- }); // If any loaders returned a redirect Response, start a new REPLACE navigation
2118
+ revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
2126
2119
 
2127
2120
  let redirect = findRedirect(results);
2128
2121
 
@@ -2186,6 +2179,7 @@ function createRouter(init) {
2186
2179
  submission
2187
2180
  } = normalizeNavigateOptions(href, opts, true);
2188
2181
  let match = getTargetMatch(matches, path);
2182
+ pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2189
2183
 
2190
2184
  if (submission && isMutationMethod(submission.formMethod)) {
2191
2185
  handleFetcherAction(key, routeId, path, match, matches, submission);
@@ -2194,7 +2188,12 @@ function createRouter(init) {
2194
2188
  // revalidations
2195
2189
 
2196
2190
 
2197
- fetchLoadMatches.set(key, [path, match, matches]);
2191
+ fetchLoadMatches.set(key, {
2192
+ routeId,
2193
+ path,
2194
+ match,
2195
+ matches
2196
+ });
2198
2197
  handleFetcherLoader(key, routeId, path, match, matches, submission);
2199
2198
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
2200
2199
  // errors, and revalidation
@@ -2300,11 +2299,8 @@ function createRouter(init) {
2300
2299
  // current fetcher which we want to keep in it's current loading state which
2301
2300
  // contains it's action submission info + action data
2302
2301
 
2303
- revalidatingFetchers.filter(_ref5 => {
2304
- let [staleKey] = _ref5;
2305
- return staleKey !== key;
2306
- }).forEach(_ref6 => {
2307
- let [staleKey] = _ref6;
2302
+ revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
2303
+ let staleKey = rf.key;
2308
2304
  let existingFetcher = state.fetchers.get(staleKey);
2309
2305
  let revalidatingFetcher = {
2310
2306
  state: "loading",
@@ -2333,10 +2329,7 @@ function createRouter(init) {
2333
2329
 
2334
2330
  fetchReloadIds.delete(key);
2335
2331
  fetchControllers.delete(key);
2336
- revalidatingFetchers.forEach(_ref7 => {
2337
- let [staleKey] = _ref7;
2338
- return fetchControllers.delete(staleKey);
2339
- });
2332
+ revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2340
2333
  let redirect = findRedirect(results);
2341
2334
 
2342
2335
  if (redirect) {
@@ -2576,16 +2569,10 @@ function createRouter(init) {
2576
2569
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2577
2570
  // then slice off the results into separate arrays so we can handle them
2578
2571
  // accordingly
2579
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(_ref8 => {
2580
- let [, href, match, fetchMatches] = _ref8;
2581
- return callLoaderOrAction("loader", createClientSideRequest(init.history, href, request.signal), match, fetchMatches, router.basename);
2582
- })]);
2572
+ 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))]);
2583
2573
  let loaderResults = results.slice(0, matchesToLoad.length);
2584
2574
  let fetcherResults = results.slice(matchesToLoad.length);
2585
- await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(_ref9 => {
2586
- let [,, match] = _ref9;
2587
- return match;
2588
- }), fetcherResults, request.signal, true)]);
2575
+ await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, request.signal, true)]);
2589
2576
  return {
2590
2577
  results,
2591
2578
  loaderResults,
@@ -2724,12 +2711,12 @@ function createRouter(init) {
2724
2711
  });
2725
2712
  }
2726
2713
 
2727
- function shouldBlockNavigation(_ref10) {
2714
+ function shouldBlockNavigation(_ref2) {
2728
2715
  let {
2729
2716
  currentLocation,
2730
2717
  nextLocation,
2731
2718
  historyAction
2732
- } = _ref10;
2719
+ } = _ref2;
2733
2720
 
2734
2721
  if (activeBlocker == null) {
2735
2722
  return;
@@ -3323,24 +3310,15 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
3323
3310
 
3324
3311
 
3325
3312
  let parsedPath = parsePath(path);
3313
+ let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
3314
+ // navigation GET submissions which run all loaders), we need to preserve
3315
+ // any incoming ?index params
3326
3316
 
3327
- try {
3328
- let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
3329
- // navigation GET submissions which run all loaders), we need to preserve
3330
- // any incoming ?index params
3331
-
3332
- if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3333
- searchParams.append("index", "");
3334
- }
3335
-
3336
- parsedPath.search = "?" + searchParams;
3337
- } catch (e) {
3338
- return {
3339
- path,
3340
- error: getInternalRouterError(400)
3341
- };
3317
+ if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3318
+ searchParams.append("index", "");
3342
3319
  }
3343
3320
 
3321
+ parsedPath.search = "?" + searchParams;
3344
3322
  return {
3345
3323
  path: createPath(parsedPath),
3346
3324
  submission
@@ -3364,25 +3342,73 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3364
3342
  }
3365
3343
 
3366
3344
  function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError, fetchLoadMatches) {
3367
- let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined; // Pick navigation matches that are net-new or qualify for revalidation
3345
+ let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3346
+ let currentUrl = history.createURL(state.location);
3347
+ let nextUrl = history.createURL(location);
3348
+ let defaultShouldRevalidate = // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
3349
+ isRevalidationRequired || // Clicked the same link, resubmitted a GET form
3350
+ currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
3351
+ currentUrl.search !== nextUrl.search; // Pick navigation matches that are net-new or qualify for revalidation
3368
3352
 
3369
3353
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3370
3354
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3371
- 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
3372
- 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
3355
+ let navigationMatches = boundaryMatches.filter((match, index) => {
3356
+ if (match.route.loader == null) {
3357
+ return false;
3358
+ } // Always call the loader on new route instances and pending defer cancellations
3373
3359
 
3374
- let revalidatingFetchers = [];
3375
- fetchLoadMatches && fetchLoadMatches.forEach((_ref11, key) => {
3376
- let [href, match, fetchMatches] = _ref11;
3377
3360
 
3378
- // This fetcher was cancelled from a prior action submission - force reload
3379
- if (cancelledFetcherLoads.includes(key)) {
3380
- revalidatingFetchers.push([key, href, match, fetchMatches]);
3381
- } else if (isRevalidationRequired) {
3382
- let shouldRevalidate = shouldRevalidateLoader(history, href, match, submission, href, match, isRevalidationRequired, actionResult);
3361
+ if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
3362
+ return true;
3363
+ } // This is the default implementation for when we revalidate. If the route
3364
+ // provides it's own implementation, then we give them full control but
3365
+ // provide this value so they can leverage it if needed after they check
3366
+ // their own specific use cases
3367
+
3368
+
3369
+ let currentRouteMatch = state.matches[index];
3370
+ let nextRouteMatch = match;
3371
+ return shouldRevalidateLoader(match, _extends({
3372
+ currentUrl,
3373
+ currentParams: currentRouteMatch.params,
3374
+ nextUrl,
3375
+ nextParams: nextRouteMatch.params
3376
+ }, submission, {
3377
+ actionResult,
3378
+ defaultShouldRevalidate: defaultShouldRevalidate || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
3379
+ }));
3380
+ }); // Pick fetcher.loads that need to be revalidated
3381
+
3382
+ let revalidatingFetchers = [];
3383
+ fetchLoadMatches && fetchLoadMatches.forEach((f, key) => {
3384
+ if (!matches.some(m => m.route.id === f.routeId)) {
3385
+ // This fetcher is not going to be present in the subsequent render so
3386
+ // there's no need to revalidate it
3387
+ return;
3388
+ } else if (cancelledFetcherLoads.includes(key)) {
3389
+ // This fetcher was cancelled from a prior action submission - force reload
3390
+ revalidatingFetchers.push(_extends({
3391
+ key
3392
+ }, f));
3393
+ } else {
3394
+ // Revalidating fetchers are decoupled from the route matches since they
3395
+ // hit a static href, so they _always_ check shouldRevalidate and the
3396
+ // default is strictly if a revalidation is explicitly required (action
3397
+ // submissions, useRevalidator, X-Remix-Revalidate).
3398
+ let shouldRevalidate = shouldRevalidateLoader(f.match, _extends({
3399
+ currentUrl,
3400
+ currentParams: state.matches[state.matches.length - 1].params,
3401
+ nextUrl,
3402
+ nextParams: matches[matches.length - 1].params
3403
+ }, submission, {
3404
+ actionResult,
3405
+ defaultShouldRevalidate
3406
+ }));
3383
3407
 
3384
3408
  if (shouldRevalidate) {
3385
- revalidatingFetchers.push([key, href, match, fetchMatches]);
3409
+ revalidatingFetchers.push(_extends({
3410
+ key
3411
+ }, f));
3386
3412
  }
3387
3413
  }
3388
3414
  });
@@ -3405,43 +3431,20 @@ function isNewRouteInstance(currentMatch, match) {
3405
3431
  return (// param change for this match, /users/123 -> /users/456
3406
3432
  currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
3407
3433
  // e.g. /files/images/avatar.jpg -> files/finances.xls
3408
- currentPath && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3434
+ currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3409
3435
  );
3410
3436
  }
3411
3437
 
3412
- function shouldRevalidateLoader(history, currentLocation, currentMatch, submission, location, match, isRevalidationRequired, actionResult) {
3413
- let currentUrl = history.createURL(currentLocation);
3414
- let currentParams = currentMatch.params;
3415
- let nextUrl = history.createURL(location);
3416
- let nextParams = match.params; // This is the default implementation as to when we revalidate. If the route
3417
- // provides it's own implementation, then we give them full control but
3418
- // provide this value so they can leverage it if needed after they check
3419
- // their own specific use cases
3420
- // Note that fetchers always provide the same current/next locations so the
3421
- // URL-based checks here don't apply to fetcher shouldRevalidate calls
3422
-
3423
- let defaultShouldRevalidate = isNewRouteInstance(currentMatch, match) || // Clicked the same link, resubmitted a GET form
3424
- currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
3425
- currentUrl.search !== nextUrl.search || // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
3426
- isRevalidationRequired;
3427
-
3428
- if (match.route.shouldRevalidate) {
3429
- let routeChoice = match.route.shouldRevalidate(_extends({
3430
- currentUrl,
3431
- currentParams,
3432
- nextUrl,
3433
- nextParams
3434
- }, submission, {
3435
- actionResult,
3436
- defaultShouldRevalidate
3437
- }));
3438
+ function shouldRevalidateLoader(loaderMatch, arg) {
3439
+ if (loaderMatch.route.shouldRevalidate) {
3440
+ let routeChoice = loaderMatch.route.shouldRevalidate(arg);
3438
3441
 
3439
3442
  if (typeof routeChoice === "boolean") {
3440
3443
  return routeChoice;
3441
3444
  }
3442
3445
  }
3443
3446
 
3444
- return defaultShouldRevalidate;
3447
+ return arg.defaultShouldRevalidate;
3445
3448
  }
3446
3449
 
3447
3450
  async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
@@ -3616,8 +3619,8 @@ function convertFormDataToSearchParams(formData) {
3616
3619
  let searchParams = new URLSearchParams();
3617
3620
 
3618
3621
  for (let [key, value] of formData.entries()) {
3619
- invariant(typeof value === "string", 'File inputs are not supported with encType "application/x-www-form-urlencoded", ' + 'please use "multipart/form-data" instead.');
3620
- searchParams.append(key, value);
3622
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
3623
+ searchParams.append(key, value instanceof File ? value.name : value);
3621
3624
  }
3622
3625
 
3623
3626
  return searchParams;
@@ -3708,7 +3711,10 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingError,
3708
3711
  } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds); // Process results from our revalidating fetchers
3709
3712
 
3710
3713
  for (let index = 0; index < revalidatingFetchers.length; index++) {
3711
- let [key,, match] = revalidatingFetchers[index];
3714
+ let {
3715
+ key,
3716
+ match
3717
+ } = revalidatingFetchers[index];
3712
3718
  invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
3713
3719
  let result = fetcherResults[index]; // Process fetcher non-redirect errors
3714
3720
 
@@ -3814,8 +3820,6 @@ function getInternalRouterError(status, _temp4) {
3814
3820
  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.";
3815
3821
  } else if (type === "defer-action") {
3816
3822
  errorMessage = "defer() is not supported in actions";
3817
- } else {
3818
- errorMessage = "Cannot submit binary form data using GET";
3819
3823
  }
3820
3824
  } else if (status === 403) {
3821
3825
  statusText = "Forbidden";