@remix-run/router 0.0.0-experimental-432fcb2e → 0.0.0-experimental-c7dd3d3a

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,12 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.15.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix a `future.v7_partialHydration` bug that would re-run loaders below the boundary on hydration if SSR loader errors bubbled to a parent boundary ([#11324](https://github.com/remix-run/react-router/pull/11324))
8
+ - Fix a `future.v7_partialHydration` bug that would consider the router uninitialized if a route did not have a loader ([#11325](https://github.com/remix-run/react-router/pull/11325))
9
+
3
10
  ## 1.15.2
4
11
 
5
12
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, DataStrategyFunction, DataStrategyFunctionArgs, DataStrategyMatch, ErrorResponse, FormEncType, FormMethod, HandlerResult as unstable_HandlerResult, HTMLFormMethod, JsonFunction, LazyRouteFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathParam, PathPattern, RedirectFunction, ShouldRevalidateFunction, ShouldRevalidateFunctionArgs, TrackedPromise, UIMatch, V7_FormMethod, } from "./utils";
1
+ export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, DataStrategyFunction as unstable_DataStrategyFunction, DataStrategyFunctionArgs as unstable_DataStrategyFunctionArgs, DataStrategyMatch as unstable_DataStrategyMatch, ErrorResponse, FormEncType, FormMethod, HandlerResult as unstable_HandlerResult, HTMLFormMethod, JsonFunction, LazyRouteFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathParam, PathPattern, RedirectFunction, ShouldRevalidateFunction, ShouldRevalidateFunctionArgs, TrackedPromise, UIMatch, V7_FormMethod, } from "./utils";
2
2
  export { AbortedDeferredError, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, redirectDocument, resolvePath, resolveTo, stripBasename, } from "./utils";
3
3
  export type { BrowserHistory, BrowserHistoryOptions, HashHistory, HashHistoryOptions, History, InitialEntry, Location, MemoryHistory, MemoryHistoryOptions, Path, To, } from "./history";
4
4
  export { Action, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, parsePath, } from "./history";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-432fcb2e
2
+ * @remix-run/router v0.0.0-experimental-c7dd3d3a
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1676,7 +1676,7 @@ function createRouter(init) {
1676
1676
  v7_partialHydration: false,
1677
1677
  v7_prependBasename: false,
1678
1678
  v7_relativeSplatPath: false,
1679
- unstable_skipActionErrorRevalidation: true
1679
+ unstable_skipActionErrorRevalidation: false
1680
1680
  }, init.future);
1681
1681
  // Cleanup function for history
1682
1682
  let unlistenHistory = null;
@@ -1728,7 +1728,26 @@ function createRouter(init) {
1728
1728
  // were marked for explicit hydration
1729
1729
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1730
1730
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1731
- initialized = initialMatches.every(m => m.route.loader && (typeof m.route.loader !== "function" || m.route.loader.hydrate !== true) && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
1731
+ let isRouteInitialized = m => {
1732
+ // No loader, nothing to initialize
1733
+ if (!m.route.loader) {
1734
+ return true;
1735
+ }
1736
+ // Explicitly opting-in to running on hydration
1737
+ if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1738
+ return false;
1739
+ }
1740
+ // Otherwise, initialized if hydrated with data or an error
1741
+ return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1742
+ };
1743
+
1744
+ // If errors exist, don't consider routes below the boundary
1745
+ if (errors) {
1746
+ let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1747
+ initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1748
+ } else {
1749
+ initialized = initialMatches.every(isRouteInitialized);
1750
+ }
1732
1751
  } else {
1733
1752
  // Without partial hydration - we're initialized if we were provided any
1734
1753
  // hydrationData - which is expected to be complete
@@ -2352,7 +2371,8 @@ function createRouter(init) {
2352
2371
  // If the user didn't explicity indicate replace behavior, replace if
2353
2372
  // we redirected to the exact same location we're currently at to avoid
2354
2373
  // double back-buttons
2355
- replace = result.response.headers.get("Location") === state.location.pathname + state.location.search;
2374
+ let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
2375
+ replace = location === state.location.pathname + state.location.search;
2356
2376
  }
2357
2377
  await startRedirectNavigation(request, result, {
2358
2378
  submission,
@@ -2870,22 +2890,10 @@ function createRouter(init) {
2870
2890
  }
2871
2891
  let location = redirect.response.headers.get("Location");
2872
2892
  invariant(location, "Expected a Location header on the redirect Response");
2893
+ location = normalizeRedirectLocation(location, new URL(request.url), basename);
2873
2894
  let redirectLocation = createLocation(state.location, location, {
2874
2895
  _isRedirect: true
2875
2896
  });
2876
- if (ABSOLUTE_URL_REGEX.test(location)) {
2877
- // Strip off the protocol+origin for same-origin + same-basename absolute redirects
2878
- let normalizedLocation = location;
2879
- let currentUrl = new URL(request.url);
2880
- let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
2881
- let isSameBasename = stripBasename(url.pathname, basename) != null;
2882
- if (url.origin === currentUrl.origin && isSameBasename) {
2883
- normalizedLocation = url.pathname + url.search + url.hash;
2884
- redirectLocation = createLocation(state.location, normalizedLocation, {
2885
- _isRedirect: true
2886
- });
2887
- }
2888
- }
2889
2897
  if (isBrowser) {
2890
2898
  let isDocumentReload = false;
2891
2899
  if (redirect.response.headers.has("X-Remix-Reload-Document")) {
@@ -2958,9 +2966,10 @@ function createRouter(init) {
2958
2966
  let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
2959
2967
  return await Promise.all(results.map((result, i) => {
2960
2968
  if (isRedirectHandlerResult(result)) {
2969
+ let response = result.result;
2961
2970
  return {
2962
2971
  type: ResultType.redirect,
2963
- response: normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
2972
+ response: normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
2964
2973
  };
2965
2974
  }
2966
2975
  return convertHandlerResultToDataResult(result);
@@ -3342,14 +3351,19 @@ function createStaticHandler(routes, opts) {
3342
3351
  * return it directly.
3343
3352
  *
3344
3353
  * - `opts.loadRouteIds` is an optional array of routeIds if you wish to only
3345
- * run a subset of route loaders on a GET request
3354
+ * run a subset of route loaders on a GET request
3346
3355
  * - `opts.requestContext` is an optional server context that will be passed
3347
- * to actions/loaders in the `context` parameter
3356
+ * to actions/loaders in the `context` parameter
3357
+ * - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
3358
+ * the bubbling of loader errors which allows single-fetch-type implementations
3359
+ * where the client will handle the bubbling and we may need to return data
3360
+ * for the handling route
3348
3361
  */
3349
3362
  async function query(request, _temp3) {
3350
3363
  let {
3351
3364
  loadRouteIds,
3352
- requestContext
3365
+ requestContext,
3366
+ skipLoaderErrorBubbling
3353
3367
  } = _temp3 === void 0 ? {} : _temp3;
3354
3368
  let url = new URL(request.url);
3355
3369
  let method = request.method;
@@ -3402,7 +3416,7 @@ function createStaticHandler(routes, opts) {
3402
3416
  activeDeferreds: null
3403
3417
  };
3404
3418
  }
3405
- let result = await queryImpl(request, location, matches, requestContext, loadRouteIds || null, null);
3419
+ let result = await queryImpl(request, location, matches, requestContext, loadRouteIds || null, skipLoaderErrorBubbling === true, null);
3406
3420
  if (isResponse(result)) {
3407
3421
  return result;
3408
3422
  }
@@ -3474,7 +3488,7 @@ function createStaticHandler(routes, opts) {
3474
3488
  pathname: location.pathname
3475
3489
  });
3476
3490
  }
3477
- let result = await queryImpl(request, location, matches, requestContext, null, match);
3491
+ let result = await queryImpl(request, location, matches, requestContext, null, false, match);
3478
3492
  if (isResponse(result)) {
3479
3493
  return result;
3480
3494
  }
@@ -3501,14 +3515,14 @@ function createStaticHandler(routes, opts) {
3501
3515
  }
3502
3516
  return undefined;
3503
3517
  }
3504
- async function queryImpl(request, location, matches, requestContext, loadRouteIds, routeMatch) {
3518
+ async function queryImpl(request, location, matches, requestContext, loadRouteIds, skipLoaderErrorBubbling, routeMatch) {
3505
3519
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
3506
3520
  try {
3507
3521
  if (isMutationMethod(request.method.toLowerCase())) {
3508
- let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, loadRouteIds, routeMatch != null);
3522
+ let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, loadRouteIds, skipLoaderErrorBubbling, routeMatch != null);
3509
3523
  return result;
3510
3524
  }
3511
- let result = await loadRouteData(request, matches, requestContext, loadRouteIds, routeMatch);
3525
+ let result = await loadRouteData(request, matches, requestContext, loadRouteIds, skipLoaderErrorBubbling, routeMatch);
3512
3526
  return isResponse(result) ? result : _extends({}, result, {
3513
3527
  actionData: null,
3514
3528
  actionHeaders: {}
@@ -3531,7 +3545,7 @@ function createStaticHandler(routes, opts) {
3531
3545
  throw e;
3532
3546
  }
3533
3547
  }
3534
- async function submit(request, matches, actionMatch, requestContext, loadRouteIds, isRouteRequest) {
3548
+ async function submit(request, matches, actionMatch, requestContext, loadRouteIds, skipLoaderErrorBubbling, isRouteRequest) {
3535
3549
  let result;
3536
3550
  if (!actionMatch.route.action && !actionMatch.route.lazy) {
3537
3551
  let error = getInternalRouterError(405, {
@@ -3609,7 +3623,7 @@ function createStaticHandler(routes, opts) {
3609
3623
  // Store off the pending error - we use it to determine which loaders
3610
3624
  // to call and will commit it when we complete the navigation
3611
3625
  let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
3612
- let context = await loadRouteData(loaderRequest, matches, requestContext, loadRouteIds, null, [boundaryMatch.route.id, result]);
3626
+ let context = await loadRouteData(loaderRequest, matches, requestContext, loadRouteIds, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
3613
3627
 
3614
3628
  // action status codes take precedence over loader status codes
3615
3629
  return _extends({}, context, {
@@ -3620,7 +3634,7 @@ function createStaticHandler(routes, opts) {
3620
3634
  } : {})
3621
3635
  });
3622
3636
  }
3623
- let context = await loadRouteData(loaderRequest, matches, requestContext, loadRouteIds, null);
3637
+ let context = await loadRouteData(loaderRequest, matches, requestContext, loadRouteIds, skipLoaderErrorBubbling, null);
3624
3638
  return _extends({}, context, {
3625
3639
  actionData: {
3626
3640
  [actionMatch.route.id]: result.data
@@ -3633,7 +3647,7 @@ function createStaticHandler(routes, opts) {
3633
3647
  } : {}
3634
3648
  });
3635
3649
  }
3636
- async function loadRouteData(request, matches, requestContext, loadRouteIds, routeMatch, pendingActionResult) {
3650
+ async function loadRouteData(request, matches, requestContext, loadRouteIds, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
3637
3651
  let isRouteRequest = routeMatch != null;
3638
3652
 
3639
3653
  // Short circuit if we have no loaders to run (queryRoute())
@@ -3673,7 +3687,7 @@ function createStaticHandler(routes, opts) {
3673
3687
 
3674
3688
  // Process and commit output from loaders
3675
3689
  let activeDeferreds = new Map();
3676
- let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds);
3690
+ let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
3677
3691
 
3678
3692
  // Add a null for any non-loader matches for proper revalidation on the client
3679
3693
  let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
@@ -3694,8 +3708,9 @@ function createStaticHandler(routes, opts) {
3694
3708
  let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
3695
3709
  return await Promise.all(results.map((result, i) => {
3696
3710
  if (isRedirectHandlerResult(result)) {
3711
+ let response = result.result;
3697
3712
  // Throw redirects and let the server handle them with an HTTP redirect
3698
- throw normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
3713
+ throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
3699
3714
  }
3700
3715
  if (isResponse(result.result) && isRouteRequest) {
3701
3716
  // For SSR single-route requests, we want to hand Responses back
@@ -3937,7 +3952,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3937
3952
  // Don't revalidate loaders by default after action 4xx/5xx responses
3938
3953
  // when the flag is enabled. They can still opt-into revalidation via
3939
3954
  // `shouldRevalidate` via `actionResult`
3940
- let shouldSkipRevalidation = skipActionErrorRevalidation && pendingActionResult && typeof pendingActionResult[1].statusCode === "number" && pendingActionResult[1].statusCode >= 400;
3955
+ let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
3956
+ let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
3941
3957
  let navigationMatches = boundaryMatches.filter((match, index) => {
3942
3958
  let {
3943
3959
  route
@@ -3976,6 +3992,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3976
3992
  nextParams: nextRouteMatch.params
3977
3993
  }, submission, {
3978
3994
  actionResult,
3995
+ unstable_actionStatus: actionStatus,
3979
3996
  defaultShouldRevalidate: shouldSkipRevalidation ? false :
3980
3997
  // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3981
3998
  isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
@@ -4040,6 +4057,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4040
4057
  nextParams: matches[matches.length - 1].params
4041
4058
  }, submission, {
4042
4059
  actionResult,
4060
+ unstable_actionStatus: actionStatus,
4043
4061
  defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
4044
4062
  }));
4045
4063
  }
@@ -4163,13 +4181,7 @@ async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLo
4163
4181
  // over the execution of the loader/action
4164
4182
  let resolve = handlerOverride => {
4165
4183
  loadedMatches.add(match.route.id);
4166
- return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) :
4167
- // TODO: What's the best thing to do here - return an empty "success" result?
4168
- // Or return a success result with the current route loader/action data?
4169
- // We strip these results out if the route didn't need to be revalidated in
4170
- // `callDataStrategy` so it doesn't matter for us. It's more of a question
4171
- // of whether exposing the current data to the user is useful?
4172
- Promise.resolve({
4184
+ return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
4173
4185
  type: ResultType.data,
4174
4186
  result: undefined
4175
4187
  });
@@ -4185,8 +4197,6 @@ async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLo
4185
4197
 
4186
4198
  // Throw if any loadRoute implementations not called since they are what
4187
4199
  // ensures a route is fully loaded
4188
- // Throw if any loadRoute implementations not called since they are what
4189
- // ensure a route is fully loaded
4190
4200
  matches.forEach(m => invariant(loadedMatches.has(m.route.id), "`match.resolve()` was not called for route id \"" + m.route.id + "\". " + "You must call `match.resolve()` on every match passed to " + "`dataStrategy` to ensure all routes are properly loaded."));
4191
4201
 
4192
4202
  // Filter out any middleware-only matches for which we didn't need to run handlers
@@ -4380,6 +4390,18 @@ function normalizeRelativeRoutingRedirectResponse(response, request, routeId, ma
4380
4390
  }
4381
4391
  return response;
4382
4392
  }
4393
+ function normalizeRedirectLocation(location, currentUrl, basename) {
4394
+ if (ABSOLUTE_URL_REGEX.test(location)) {
4395
+ // Strip off the protocol+origin for same-origin + same-basename absolute redirects
4396
+ let normalizedLocation = location;
4397
+ let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
4398
+ let isSameBasename = stripBasename(url.pathname, basename) != null;
4399
+ if (url.origin === currentUrl.origin && isSameBasename) {
4400
+ return url.pathname + url.search + url.hash;
4401
+ }
4402
+ }
4403
+ return location;
4404
+ }
4383
4405
 
4384
4406
  // Utility method for creating the Request instances for loaders/actions during
4385
4407
  // client-side navigations and fetches. During SSR we will always have a
@@ -4431,7 +4453,7 @@ function convertSearchParamsToFormData(searchParams) {
4431
4453
  }
4432
4454
  return formData;
4433
4455
  }
4434
- function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds) {
4456
+ function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
4435
4457
  // Fill in loaderData/errors from our loaders
4436
4458
  let loaderData = {};
4437
4459
  let errors = null;
@@ -4445,9 +4467,6 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingActionRe
4445
4467
  let id = matchesToLoad[index].route.id;
4446
4468
  invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
4447
4469
  if (isErrorResult(result)) {
4448
- // Look upwards from the matched route for the closest ancestor
4449
- // error boundary, defaulting to the root match
4450
- let boundaryMatch = findNearestBoundary(matches, id);
4451
4470
  let error = result.error;
4452
4471
  // If we have a pending action error, we report it at the highest-route
4453
4472
  // that throws a loader error, and then clear it out to indicate that
@@ -4457,10 +4476,16 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingActionRe
4457
4476
  pendingError = undefined;
4458
4477
  }
4459
4478
  errors = errors || {};
4460
-
4461
- // Prefer higher error values if lower errors bubble to the same boundary
4462
- if (errors[boundaryMatch.route.id] == null) {
4463
- errors[boundaryMatch.route.id] = error;
4479
+ if (skipLoaderErrorBubbling) {
4480
+ errors[id] = error;
4481
+ } else {
4482
+ // Look upwards from the matched route for the closest ancestor error
4483
+ // boundary, defaulting to the root match. Prefer higher error values
4484
+ // if lower errors bubble to the same boundary
4485
+ let boundaryMatch = findNearestBoundary(matches, id);
4486
+ if (errors[boundaryMatch.route.id] == null) {
4487
+ errors[boundaryMatch.route.id] = error;
4488
+ }
4464
4489
  }
4465
4490
 
4466
4491
  // Clear our any prior loaderData for the throwing route
@@ -4521,7 +4546,8 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingAction
4521
4546
  let {
4522
4547
  loaderData,
4523
4548
  errors
4524
- } = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds);
4549
+ } = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
4550
+ );
4525
4551
 
4526
4552
  // Process results from our revalidating fetchers
4527
4553
  for (let index = 0; index < revalidatingFetchers.length; index++) {