@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/dist/router.js CHANGED
@@ -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
  *
@@ -418,10 +418,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
418
418
  });
419
419
  }
420
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.");
421
+ 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).");
425
422
  }
426
423
  }
427
424
 
@@ -1209,6 +1206,12 @@ class DeferredData {
1209
1206
  [key]: this.trackPromise(key, value)
1210
1207
  });
1211
1208
  }, {});
1209
+
1210
+ if (this.done) {
1211
+ // All incoming values were resolved
1212
+ this.unlistenAbortSignal();
1213
+ }
1214
+
1212
1215
  this.init = responseInit;
1213
1216
  }
1214
1217
 
@@ -1396,11 +1399,11 @@ class ErrorResponse {
1396
1399
  }
1397
1400
  /**
1398
1401
  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
1399
- * Response throw from an action/loader
1402
+ * Response thrown from an action/loader
1400
1403
  */
1401
1404
 
1402
- function isRouteErrorResponse(e) {
1403
- return e instanceof ErrorResponse;
1405
+ function isRouteErrorResponse(error) {
1406
+ return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
1404
1407
  }
1405
1408
 
1406
1409
  const validMutationMethodsArr = ["post", "put", "patch", "delete"];
@@ -1856,10 +1859,12 @@ function createRouter(init) {
1856
1859
  }
1857
1860
  });
1858
1861
  return;
1859
- } // Short circuit if it's only a hash change
1862
+ } // Short circuit if it's only a hash change and not a mutation submission
1863
+ // For example, on /page#hash and submit a <Form method="post"> which will
1864
+ // default to a navigation to /page
1860
1865
 
1861
1866
 
1862
- if (isHashChangeOnly(state.location, location)) {
1867
+ if (isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
1863
1868
  completeNavigation(location, {
1864
1869
  matches
1865
1870
  });
@@ -2073,9 +2078,8 @@ function createRouter(init) {
2073
2078
 
2074
2079
 
2075
2080
  if (!isUninterruptedRevalidation) {
2076
- revalidatingFetchers.forEach(_ref2 => {
2077
- let [key] = _ref2;
2078
- let fetcher = state.fetchers.get(key);
2081
+ revalidatingFetchers.forEach(rf => {
2082
+ let fetcher = state.fetchers.get(rf.key);
2079
2083
  let revalidatingFetcher = {
2080
2084
  state: "loading",
2081
2085
  data: fetcher && fetcher.data,
@@ -2085,7 +2089,7 @@ function createRouter(init) {
2085
2089
  formData: undefined,
2086
2090
  " _hasFetcherDoneAnything ": true
2087
2091
  };
2088
- state.fetchers.set(key, revalidatingFetcher);
2092
+ state.fetchers.set(rf.key, revalidatingFetcher);
2089
2093
  });
2090
2094
  let actionData = pendingActionData || state.actionData;
2091
2095
  updateState(_extends({
@@ -2100,10 +2104,7 @@ function createRouter(init) {
2100
2104
  }
2101
2105
 
2102
2106
  pendingNavigationLoadId = ++incrementingLoadId;
2103
- revalidatingFetchers.forEach(_ref3 => {
2104
- let [key] = _ref3;
2105
- return fetchControllers.set(key, pendingNavigationController);
2106
- });
2107
+ revalidatingFetchers.forEach(rf => fetchControllers.set(rf.key, pendingNavigationController));
2107
2108
  let {
2108
2109
  results,
2109
2110
  loaderResults,
@@ -2119,10 +2120,7 @@ function createRouter(init) {
2119
2120
  // reassigned to new controllers for the next navigation
2120
2121
 
2121
2122
 
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
2123
+ revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
2126
2124
 
2127
2125
  let redirect = findRedirect(results);
2128
2126
 
@@ -2186,6 +2184,7 @@ function createRouter(init) {
2186
2184
  submission
2187
2185
  } = normalizeNavigateOptions(href, opts, true);
2188
2186
  let match = getTargetMatch(matches, path);
2187
+ pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2189
2188
 
2190
2189
  if (submission && isMutationMethod(submission.formMethod)) {
2191
2190
  handleFetcherAction(key, routeId, path, match, matches, submission);
@@ -2194,7 +2193,12 @@ function createRouter(init) {
2194
2193
  // revalidations
2195
2194
 
2196
2195
 
2197
- fetchLoadMatches.set(key, [path, match, matches]);
2196
+ fetchLoadMatches.set(key, {
2197
+ routeId,
2198
+ path,
2199
+ match,
2200
+ matches
2201
+ });
2198
2202
  handleFetcherLoader(key, routeId, path, match, matches, submission);
2199
2203
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
2200
2204
  // errors, and revalidation
@@ -2300,11 +2304,8 @@ function createRouter(init) {
2300
2304
  // current fetcher which we want to keep in it's current loading state which
2301
2305
  // contains it's action submission info + action data
2302
2306
 
2303
- revalidatingFetchers.filter(_ref5 => {
2304
- let [staleKey] = _ref5;
2305
- return staleKey !== key;
2306
- }).forEach(_ref6 => {
2307
- let [staleKey] = _ref6;
2307
+ revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
2308
+ let staleKey = rf.key;
2308
2309
  let existingFetcher = state.fetchers.get(staleKey);
2309
2310
  let revalidatingFetcher = {
2310
2311
  state: "loading",
@@ -2333,10 +2334,7 @@ function createRouter(init) {
2333
2334
 
2334
2335
  fetchReloadIds.delete(key);
2335
2336
  fetchControllers.delete(key);
2336
- revalidatingFetchers.forEach(_ref7 => {
2337
- let [staleKey] = _ref7;
2338
- return fetchControllers.delete(staleKey);
2339
- });
2337
+ revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2340
2338
  let redirect = findRedirect(results);
2341
2339
 
2342
2340
  if (redirect) {
@@ -2576,16 +2574,10 @@ function createRouter(init) {
2576
2574
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2577
2575
  // then slice off the results into separate arrays so we can handle them
2578
2576
  // 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
- })]);
2577
+ 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
2578
  let loaderResults = results.slice(0, matchesToLoad.length);
2584
2579
  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)]);
2580
+ await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, request.signal, true)]);
2589
2581
  return {
2590
2582
  results,
2591
2583
  loaderResults,
@@ -2724,12 +2716,12 @@ function createRouter(init) {
2724
2716
  });
2725
2717
  }
2726
2718
 
2727
- function shouldBlockNavigation(_ref10) {
2719
+ function shouldBlockNavigation(_ref2) {
2728
2720
  let {
2729
2721
  currentLocation,
2730
2722
  nextLocation,
2731
2723
  historyAction
2732
- } = _ref10;
2724
+ } = _ref2;
2733
2725
 
2734
2726
  if (activeBlocker == null) {
2735
2727
  return;
@@ -3323,24 +3315,15 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
3323
3315
 
3324
3316
 
3325
3317
  let parsedPath = parsePath(path);
3318
+ let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
3319
+ // navigation GET submissions which run all loaders), we need to preserve
3320
+ // any incoming ?index params
3326
3321
 
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
- };
3322
+ if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
3323
+ searchParams.append("index", "");
3342
3324
  }
3343
3325
 
3326
+ parsedPath.search = "?" + searchParams;
3344
3327
  return {
3345
3328
  path: createPath(parsedPath),
3346
3329
  submission
@@ -3364,25 +3347,73 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3364
3347
  }
3365
3348
 
3366
3349
  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
3350
+ let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3351
+ let currentUrl = history.createURL(state.location);
3352
+ let nextUrl = history.createURL(location);
3353
+ let defaultShouldRevalidate = // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
3354
+ isRevalidationRequired || // Clicked the same link, resubmitted a GET form
3355
+ currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
3356
+ currentUrl.search !== nextUrl.search; // Pick navigation matches that are net-new or qualify for revalidation
3368
3357
 
3369
3358
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3370
3359
  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
3360
+ let navigationMatches = boundaryMatches.filter((match, index) => {
3361
+ if (match.route.loader == null) {
3362
+ return false;
3363
+ } // Always call the loader on new route instances and pending defer cancellations
3373
3364
 
3374
- let revalidatingFetchers = [];
3375
- fetchLoadMatches && fetchLoadMatches.forEach((_ref11, key) => {
3376
- let [href, match, fetchMatches] = _ref11;
3377
3365
 
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);
3366
+ if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
3367
+ return true;
3368
+ } // This is the default implementation for when we revalidate. If the route
3369
+ // provides it's own implementation, then we give them full control but
3370
+ // provide this value so they can leverage it if needed after they check
3371
+ // their own specific use cases
3372
+
3373
+
3374
+ let currentRouteMatch = state.matches[index];
3375
+ let nextRouteMatch = match;
3376
+ return shouldRevalidateLoader(match, _extends({
3377
+ currentUrl,
3378
+ currentParams: currentRouteMatch.params,
3379
+ nextUrl,
3380
+ nextParams: nextRouteMatch.params
3381
+ }, submission, {
3382
+ actionResult,
3383
+ defaultShouldRevalidate: defaultShouldRevalidate || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
3384
+ }));
3385
+ }); // Pick fetcher.loads that need to be revalidated
3386
+
3387
+ let revalidatingFetchers = [];
3388
+ fetchLoadMatches && fetchLoadMatches.forEach((f, key) => {
3389
+ if (!matches.some(m => m.route.id === f.routeId)) {
3390
+ // This fetcher is not going to be present in the subsequent render so
3391
+ // there's no need to revalidate it
3392
+ return;
3393
+ } else if (cancelledFetcherLoads.includes(key)) {
3394
+ // This fetcher was cancelled from a prior action submission - force reload
3395
+ revalidatingFetchers.push(_extends({
3396
+ key
3397
+ }, f));
3398
+ } else {
3399
+ // Revalidating fetchers are decoupled from the route matches since they
3400
+ // hit a static href, so they _always_ check shouldRevalidate and the
3401
+ // default is strictly if a revalidation is explicitly required (action
3402
+ // submissions, useRevalidator, X-Remix-Revalidate).
3403
+ let shouldRevalidate = shouldRevalidateLoader(f.match, _extends({
3404
+ currentUrl,
3405
+ currentParams: state.matches[state.matches.length - 1].params,
3406
+ nextUrl,
3407
+ nextParams: matches[matches.length - 1].params
3408
+ }, submission, {
3409
+ actionResult,
3410
+ defaultShouldRevalidate
3411
+ }));
3383
3412
 
3384
3413
  if (shouldRevalidate) {
3385
- revalidatingFetchers.push([key, href, match, fetchMatches]);
3414
+ revalidatingFetchers.push(_extends({
3415
+ key
3416
+ }, f));
3386
3417
  }
3387
3418
  }
3388
3419
  });
@@ -3405,43 +3436,20 @@ function isNewRouteInstance(currentMatch, match) {
3405
3436
  return (// param change for this match, /users/123 -> /users/456
3406
3437
  currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
3407
3438
  // e.g. /files/images/avatar.jpg -> files/finances.xls
3408
- currentPath && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3439
+ currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
3409
3440
  );
3410
3441
  }
3411
3442
 
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
- }));
3443
+ function shouldRevalidateLoader(loaderMatch, arg) {
3444
+ if (loaderMatch.route.shouldRevalidate) {
3445
+ let routeChoice = loaderMatch.route.shouldRevalidate(arg);
3438
3446
 
3439
3447
  if (typeof routeChoice === "boolean") {
3440
3448
  return routeChoice;
3441
3449
  }
3442
3450
  }
3443
3451
 
3444
- return defaultShouldRevalidate;
3452
+ return arg.defaultShouldRevalidate;
3445
3453
  }
3446
3454
 
3447
3455
  async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
@@ -3616,8 +3624,8 @@ function convertFormDataToSearchParams(formData) {
3616
3624
  let searchParams = new URLSearchParams();
3617
3625
 
3618
3626
  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);
3627
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
3628
+ searchParams.append(key, value instanceof File ? value.name : value);
3621
3629
  }
3622
3630
 
3623
3631
  return searchParams;
@@ -3708,7 +3716,10 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingError,
3708
3716
  } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds); // Process results from our revalidating fetchers
3709
3717
 
3710
3718
  for (let index = 0; index < revalidatingFetchers.length; index++) {
3711
- let [key,, match] = revalidatingFetchers[index];
3719
+ let {
3720
+ key,
3721
+ match
3722
+ } = revalidatingFetchers[index];
3712
3723
  invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
3713
3724
  let result = fetcherResults[index]; // Process fetcher non-redirect errors
3714
3725
 
@@ -3814,8 +3825,6 @@ function getInternalRouterError(status, _temp4) {
3814
3825
  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
3826
  } else if (type === "defer-action") {
3816
3827
  errorMessage = "defer() is not supported in actions";
3817
- } else {
3818
- errorMessage = "Cannot submit binary form data using GET";
3819
3828
  }
3820
3829
  } else if (status === 403) {
3821
3830
  statusText = "Forbidden";