@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 +15 -0
- package/dist/router.cjs.js +104 -43
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +103 -43
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +104 -43
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +4 -1
- package/package.json +1 -1
- package/router.ts +84 -37
- package/utils.ts +22 -3
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
|
package/dist/router.cjs.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
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
|
|
3589
|
-
//
|
|
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 =
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
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
|
|
4187
|
+
return {
|
|
4188
|
+
result,
|
|
4189
|
+
idx: i
|
|
4190
|
+
};
|
|
4130
4191
|
}
|
|
4131
4192
|
}
|
|
4132
4193
|
}
|