@remix-run/router 1.7.1 → 1.7.2-pre.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.7.2-pre.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [REMOVE] Fix additional edge case for #10674 ([#10709](https://github.com/remix-run/react-router/pull/10709))
8
+
9
+ ## 1.7.2-pre.0
10
+
11
+ ### Patch Changes
12
+
13
+ - Trigger an error if a `defer` promise resolves/rejects with `undefined` in order to match the behavior of loaders and actions which must return a value or `null` ([#10690](https://github.com/remix-run/react-router/pull/10690))
14
+ - Properly handle fetcher redirects interrupted by normal navigations ([#10674](https://github.com/remix-run/react-router/pull/10674))
15
+ - Initial-load fetchers should not automatically revalidate on GET navigations ([#10688](https://github.com/remix-run/react-router/pull/10688))
16
+ - Enhance the return type of `Route.lazy` to prohibit returning an empty object ([#10634](https://github.com/remix-run/react-router/pull/10634))
17
+
3
18
  ## 1.7.1
4
19
 
5
20
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.7.1
2
+ * @remix-run/router v1.7.2-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1279,7 +1279,7 @@ class DeferredData {
1279
1279
 
1280
1280
  // We store a little wrapper promise that will be extended with
1281
1281
  // _data/_error props upon resolve/reject
1282
- let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error));
1282
+ let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, undefined, data), error => this.onSettle(promise, key, error));
1283
1283
 
1284
1284
  // Register rejection listeners to avoid uncaught promise rejections on
1285
1285
  // errors or aborted deferred values
@@ -1302,7 +1302,18 @@ class DeferredData {
1302
1302
  // Nothing left to abort!
1303
1303
  this.unlistenAbortSignal();
1304
1304
  }
1305
- if (error) {
1305
+
1306
+ // If the promise was resolved/rejected with undefined, we'll throw an error as you
1307
+ // should always resolve with a value or null
1308
+ if (error === undefined && data === undefined) {
1309
+ let undefinedError = new Error("Deferred data for key \"" + key + "\" resolved/rejected with `undefined`, " + "you must resolve/reject with a value or `null`.");
1310
+ Object.defineProperty(promise, "_error", {
1311
+ get: () => undefinedError
1312
+ });
1313
+ this.emit(false, key);
1314
+ return Promise.reject(undefinedError);
1315
+ }
1316
+ if (data === undefined) {
1306
1317
  Object.defineProperty(promise, "_error", {
1307
1318
  get: () => error
1308
1319
  });
@@ -2184,6 +2195,7 @@ function createRouter(init) {
2184
2195
  // about to reload. Note that if this is an action reload we would have
2185
2196
  // already cancelled all pending deferreds so this would be a no-op
2186
2197
  cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
2198
+ pendingNavigationLoadId = ++incrementingLoadId;
2187
2199
 
2188
2200
  // Short circuit if we have no loaders to run
2189
2201
  if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
@@ -2224,7 +2236,6 @@ function createRouter(init) {
2224
2236
  fetchers: new Map(state.fetchers)
2225
2237
  } : {}));
2226
2238
  }
2227
- pendingNavigationLoadId = ++incrementingLoadId;
2228
2239
  revalidatingFetchers.forEach(rf => {
2229
2240
  if (fetchControllers.has(rf.key)) {
2230
2241
  abortFetcher(rf.key);
@@ -2264,7 +2275,14 @@ function createRouter(init) {
2264
2275
  // If any loaders returned a redirect Response, start a new REPLACE navigation
2265
2276
  let redirect = findRedirect(results);
2266
2277
  if (redirect) {
2267
- await startRedirectNavigation(state, redirect, {
2278
+ if (redirect.idx >= matchesToLoad.length) {
2279
+ // If this redirect came from a fetcher make sure we mark it in
2280
+ // fetchRedirectIds so it doesn't get revalidated on the next set of
2281
+ // loader executions
2282
+ let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2283
+ fetchRedirectIds.add(fetcherKey);
2284
+ }
2285
+ await startRedirectNavigation(state, redirect.result, {
2268
2286
  replace
2269
2287
  });
2270
2288
  return {
@@ -2370,6 +2388,7 @@ function createRouter(init) {
2370
2388
  let abortController = new AbortController();
2371
2389
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2372
2390
  fetchControllers.set(key, abortController);
2391
+ let originatingLoadId = incrementingLoadId;
2373
2392
  let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2374
2393
  if (fetchRequest.signal.aborted) {
2375
2394
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2381,16 +2400,29 @@ function createRouter(init) {
2381
2400
  }
2382
2401
  if (isRedirectResult(actionResult)) {
2383
2402
  fetchControllers.delete(key);
2384
- fetchRedirectIds.add(key);
2385
- let loadingFetcher = getLoadingFetcher(submission);
2386
- state.fetchers.set(key, loadingFetcher);
2387
- updateState({
2388
- fetchers: new Map(state.fetchers)
2389
- });
2390
- return startRedirectNavigation(state, actionResult, {
2391
- submission,
2392
- isFetchActionRedirect: true
2393
- });
2403
+ if (pendingNavigationLoadId > originatingLoadId) {
2404
+ // A new navigation was kicked off after our action started, so that
2405
+ // should take precedence over this redirect navigation. We already
2406
+ // set isRevalidationRequired so all loaders for the new route should
2407
+ // fire unless opted out via shouldRevalidate
2408
+ let doneFetcher = getDoneFetcher(undefined);
2409
+ state.fetchers.set(key, doneFetcher);
2410
+ updateState({
2411
+ fetchers: new Map(state.fetchers)
2412
+ });
2413
+ return;
2414
+ } else {
2415
+ fetchRedirectIds.add(key);
2416
+ let loadingFetcher = getLoadingFetcher(submission);
2417
+ state.fetchers.set(key, loadingFetcher);
2418
+ updateState({
2419
+ fetchers: new Map(state.fetchers)
2420
+ });
2421
+ return startRedirectNavigation(state, actionResult, {
2422
+ submission,
2423
+ isFetchActionRedirect: true
2424
+ });
2425
+ }
2394
2426
  }
2395
2427
 
2396
2428
  // Process any non-redirect errors thrown
@@ -2454,7 +2486,14 @@ function createRouter(init) {
2454
2486
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2455
2487
  let redirect = findRedirect(results);
2456
2488
  if (redirect) {
2457
- return startRedirectNavigation(state, redirect);
2489
+ if (redirect.idx >= matchesToLoad.length) {
2490
+ // If this redirect came from a fetcher make sure we mark it in
2491
+ // fetchRedirectIds so it doesn't get revalidated on the next set of
2492
+ // loader executions
2493
+ let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2494
+ fetchRedirectIds.add(fetcherKey);
2495
+ }
2496
+ return startRedirectNavigation(state, redirect.result);
2458
2497
  }
2459
2498
 
2460
2499
  // Process and commit output from loaders
@@ -2511,6 +2550,7 @@ function createRouter(init) {
2511
2550
  let abortController = new AbortController();
2512
2551
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2513
2552
  fetchControllers.set(key, abortController);
2553
+ let originatingLoadId = incrementingLoadId;
2514
2554
  let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
2515
2555
 
2516
2556
  // Deferred isn't supported for fetcher loads, await everything and treat it
@@ -2532,9 +2572,20 @@ function createRouter(init) {
2532
2572
 
2533
2573
  // If the loader threw a redirect Response, start a new REPLACE navigation
2534
2574
  if (isRedirectResult(result)) {
2535
- fetchRedirectIds.add(key);
2536
- await startRedirectNavigation(state, result);
2537
- return;
2575
+ if (pendingNavigationLoadId > originatingLoadId) {
2576
+ // A new navigation was kicked off after our loader started, so that
2577
+ // should take precedence over this redirect navigation
2578
+ let doneFetcher = getDoneFetcher(undefined);
2579
+ state.fetchers.set(key, doneFetcher);
2580
+ updateState({
2581
+ fetchers: new Map(state.fetchers)
2582
+ });
2583
+ return;
2584
+ } else {
2585
+ fetchRedirectIds.add(key);
2586
+ await startRedirectNavigation(state, result);
2587
+ return;
2588
+ }
2538
2589
  }
2539
2590
 
2540
2591
  // Process any non-redirect errors thrown
@@ -3571,7 +3622,9 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3571
3622
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
3572
3623
 
3573
3624
  // If the fetcher path no longer matches, push it in with null matches so
3574
- // we can trigger a 404 in callLoadersAndMaybeResolveData
3625
+ // we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is
3626
+ // currently only a use-case for Remix HMR where the route tree can change
3627
+ // at runtime and remove a route previously loaded via a fetcher
3575
3628
  if (!fetcherMatches) {
3576
3629
  revalidatingFetchers.push({
3577
3630
  key,
@@ -3585,30 +3638,35 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3585
3638
  }
3586
3639
 
3587
3640
  // Revalidating fetchers are decoupled from the route matches since they
3588
- // load from a static href. They only set `defaultShouldRevalidate` on
3589
- // explicit revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3590
- //
3591
- // They automatically revalidate without even calling shouldRevalidate if:
3592
- // - They were cancelled
3593
- // - They're in the middle of their first load and therefore this is still
3594
- // an initial load and not a revalidation
3595
- //
3596
- // If neither of those is true, then they _always_ check shouldRevalidate
3641
+ // load from a static href. They revalidate based on explicit revalidation
3642
+ // (submission, useRevalidator, or X-Remix-Revalidate)
3597
3643
  let fetcher = state.fetchers.get(key);
3598
- let isPerformingInitialLoad = fetcher && fetcher.state !== "idle" && fetcher.data === undefined &&
3599
- // If a fetcher.load redirected then it'll be "loading" without any data
3600
- // so ensure we're not processing the redirect from this fetcher
3601
- !fetchRedirectIds.has(key);
3602
3644
  let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
3603
- let shouldRevalidate = cancelledFetcherLoads.includes(key) || isPerformingInitialLoad || shouldRevalidateLoader(fetcherMatch, _extends({
3604
- currentUrl,
3605
- currentParams: state.matches[state.matches.length - 1].params,
3606
- nextUrl,
3607
- nextParams: matches[matches.length - 1].params
3608
- }, submission, {
3609
- actionResult,
3610
- defaultShouldRevalidate: isRevalidationRequired
3611
- }));
3645
+ let shouldRevalidate = false;
3646
+ if (fetchRedirectIds.has(key)) {
3647
+ // Never trigger a revalidation of an actively redirecting fetcher
3648
+ shouldRevalidate = false;
3649
+ } else if (cancelledFetcherLoads.includes(key)) {
3650
+ // Always revalidate if the fetcher was cancelled
3651
+ shouldRevalidate = true;
3652
+ } else if (fetcher && fetcher.state !== "idle" && fetcher.data === undefined) {
3653
+ // If the fetcher hasn't ever completed loading yet, then this isn't a
3654
+ // revalidation, it would just be a brand new load if an explicit
3655
+ // revalidation is required
3656
+ shouldRevalidate = isRevalidationRequired;
3657
+ } else {
3658
+ // Otherwise fall back on any user-defined shouldRevalidate, defaulting
3659
+ // to explicit revalidations only
3660
+ shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
3661
+ currentUrl,
3662
+ currentParams: state.matches[state.matches.length - 1].params,
3663
+ nextUrl,
3664
+ nextParams: matches[matches.length - 1].params
3665
+ }, submission, {
3666
+ actionResult,
3667
+ defaultShouldRevalidate: isRevalidationRequired
3668
+ }));
3669
+ }
3612
3670
  if (shouldRevalidate) {
3613
3671
  revalidatingFetchers.push({
3614
3672
  key,
@@ -4126,7 +4184,10 @@ function findRedirect(results) {
4126
4184
  for (let i = results.length - 1; i >= 0; i--) {
4127
4185
  let result = results[i];
4128
4186
  if (isRedirectResult(result)) {
4129
- return result;
4187
+ return {
4188
+ result,
4189
+ idx: i
4190
+ };
4130
4191
  }
4131
4192
  }
4132
4193
  }