@tanstack/react-router 1.45.2 → 1.45.3
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/cjs/CatchBoundary.cjs +1 -1
- package/dist/cjs/CatchBoundary.cjs.map +1 -1
- package/dist/cjs/CatchBoundary.d.cts +1 -1
- package/dist/cjs/Match.cjs +24 -34
- package/dist/cjs/Match.cjs.map +1 -1
- package/dist/cjs/Matches.cjs +1 -1
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +4 -2
- package/dist/cjs/RouterProvider.cjs +0 -8
- package/dist/cjs/RouterProvider.cjs.map +1 -1
- package/dist/cjs/RouterProvider.d.cts +1 -4
- package/dist/cjs/Transitioner.cjs +5 -1
- package/dist/cjs/Transitioner.cjs.map +1 -1
- package/dist/cjs/index.cjs +0 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +1 -0
- package/dist/cjs/router.cjs +459 -406
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +22 -10
- package/dist/esm/CatchBoundary.d.ts +1 -1
- package/dist/esm/CatchBoundary.js +1 -1
- package/dist/esm/CatchBoundary.js.map +1 -1
- package/dist/esm/Match.js +24 -34
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.d.ts +4 -2
- package/dist/esm/Matches.js +1 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.d.ts +1 -4
- package/dist/esm/RouterProvider.js +1 -9
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/Transitioner.js +5 -1
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -2
- package/dist/esm/route.d.ts +1 -0
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +22 -10
- package/dist/esm/router.js +461 -408
- package/dist/esm/router.js.map +1 -1
- package/package.json +2 -2
- package/src/CatchBoundary.tsx +7 -3
- package/src/Match.tsx +45 -36
- package/src/Matches.tsx +5 -3
- package/src/RouterProvider.tsx +0 -11
- package/src/Transitioner.tsx +5 -1
- package/src/index.tsx +0 -1
- package/src/route.ts +1 -0
- package/src/router.ts +639 -565
package/src/router.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
pick,
|
|
13
13
|
replaceEqualDeep,
|
|
14
14
|
} from './utils'
|
|
15
|
-
import { getRouteMatch } from './RouterProvider'
|
|
16
15
|
import {
|
|
17
16
|
cleanPath,
|
|
18
17
|
interpolatePath,
|
|
@@ -365,6 +364,12 @@ export interface RouterOptions<
|
|
|
365
364
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#trailingslash-property)
|
|
366
365
|
*/
|
|
367
366
|
trailingSlash?: TTrailingSlashOption
|
|
367
|
+
/**
|
|
368
|
+
* Defaults to `typeof document !== 'undefined'`
|
|
369
|
+
* While usually automatic, sometimes it can be useful to force the router into a server-side state, e.g. when using the router in a non-browser environment that has access to a global.document object.
|
|
370
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#isserver property)
|
|
371
|
+
*/
|
|
372
|
+
isServer?: boolean
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
export interface RouterTransformer {
|
|
@@ -381,6 +386,7 @@ export interface RouterState<
|
|
|
381
386
|
TRouteMatch = MakeRouteMatch<TRouteTree>,
|
|
382
387
|
> {
|
|
383
388
|
status: 'pending' | 'idle'
|
|
389
|
+
loadedAt: number
|
|
384
390
|
isLoading: boolean
|
|
385
391
|
isTransitioning: boolean
|
|
386
392
|
matches: Array<TRouteMatch>
|
|
@@ -517,7 +523,6 @@ export class Router<
|
|
|
517
523
|
)}`
|
|
518
524
|
resetNextScroll = true
|
|
519
525
|
shouldViewTransition?: boolean = undefined
|
|
520
|
-
latestLoadPromise: Promise<void> = Promise.resolve()
|
|
521
526
|
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
522
527
|
dehydratedData?: TDehydrated
|
|
523
528
|
viewTransitionPromise?: ControlledPromise<true>
|
|
@@ -561,6 +566,7 @@ export class Router<
|
|
|
561
566
|
routesById!: RoutesById<TRouteTree>
|
|
562
567
|
routesByPath!: RoutesByPath<TRouteTree>
|
|
563
568
|
flatRoutes!: Array<AnyRoute>
|
|
569
|
+
isServer!: boolean
|
|
564
570
|
|
|
565
571
|
/**
|
|
566
572
|
* @deprecated Use the `createRouter` function instead
|
|
@@ -588,8 +594,6 @@ export class Router<
|
|
|
588
594
|
}
|
|
589
595
|
}
|
|
590
596
|
|
|
591
|
-
isServer = typeof document === 'undefined'
|
|
592
|
-
|
|
593
597
|
// These are default implementations that can optionally be overridden
|
|
594
598
|
// by the router provider once rendered. We provide these so that the
|
|
595
599
|
// router can be used in a non-react environment if necessary
|
|
@@ -615,6 +619,8 @@ export class Router<
|
|
|
615
619
|
...newOptions,
|
|
616
620
|
}
|
|
617
621
|
|
|
622
|
+
this.isServer = this.options.isServer ?? typeof document === 'undefined'
|
|
623
|
+
|
|
618
624
|
if (
|
|
619
625
|
!this.basepath ||
|
|
620
626
|
(newOptions.basepath && newOptions.basepath !== previousOptions.basepath)
|
|
@@ -637,11 +643,11 @@ export class Router<
|
|
|
637
643
|
) {
|
|
638
644
|
this.history =
|
|
639
645
|
this.options.history ??
|
|
640
|
-
(
|
|
641
|
-
?
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
646
|
+
(this.isServer
|
|
647
|
+
? createMemoryHistory({
|
|
648
|
+
initialEntries: [this.basepath || '/'],
|
|
649
|
+
})
|
|
650
|
+
: createBrowserHistory())
|
|
645
651
|
this.latestLocation = this.parseLocation()
|
|
646
652
|
}
|
|
647
653
|
|
|
@@ -808,12 +814,6 @@ export class Router<
|
|
|
808
814
|
})
|
|
809
815
|
}
|
|
810
816
|
|
|
811
|
-
checkLatest = (promise: Promise<void>): void => {
|
|
812
|
-
if (this.latestLoadPromise !== promise) {
|
|
813
|
-
throw this.latestLoadPromise
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
817
|
parseLocation = (
|
|
818
818
|
previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
|
|
819
819
|
): ParsedLocation<FullSearchSchema<TRouteTree>> => {
|
|
@@ -827,7 +827,7 @@ export class Router<
|
|
|
827
827
|
const searchStr = this.options.stringifySearch(parsedSearch)
|
|
828
828
|
|
|
829
829
|
return {
|
|
830
|
-
pathname
|
|
830
|
+
pathname,
|
|
831
831
|
searchStr,
|
|
832
832
|
search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
|
|
833
833
|
hash: hash.split('#').reverse()[0] ?? '',
|
|
@@ -1044,7 +1044,7 @@ export class Router<
|
|
|
1044
1044
|
// Waste not, want not. If we already have a match for this route,
|
|
1045
1045
|
// reuse it. This is important for layout routes, which might stick
|
|
1046
1046
|
// around between navigation actions that only change leaf routes.
|
|
1047
|
-
const existingMatch =
|
|
1047
|
+
const existingMatch = this.getMatch(matchId)
|
|
1048
1048
|
|
|
1049
1049
|
const cause = this.state.matches.find((d) => d.id === matchId)
|
|
1050
1050
|
? 'stay'
|
|
@@ -1060,17 +1060,10 @@ export class Router<
|
|
|
1060
1060
|
}
|
|
1061
1061
|
} else {
|
|
1062
1062
|
const status =
|
|
1063
|
-
route.options.loader || route.options.beforeLoad
|
|
1063
|
+
route.options.loader || route.options.beforeLoad || route.lazyFn
|
|
1064
1064
|
? 'pending'
|
|
1065
1065
|
: 'success'
|
|
1066
1066
|
|
|
1067
|
-
const loadPromise = createControlledPromise<void>()
|
|
1068
|
-
|
|
1069
|
-
// If it's already a success, resolve the load promise
|
|
1070
|
-
if (status === 'success') {
|
|
1071
|
-
loadPromise.resolve()
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
1067
|
match = {
|
|
1075
1068
|
id: matchId,
|
|
1076
1069
|
index,
|
|
@@ -1080,12 +1073,10 @@ export class Router<
|
|
|
1080
1073
|
updatedAt: Date.now(),
|
|
1081
1074
|
search: {} as any,
|
|
1082
1075
|
searchError: undefined,
|
|
1083
|
-
status
|
|
1076
|
+
status,
|
|
1084
1077
|
isFetching: false,
|
|
1085
1078
|
error: undefined,
|
|
1086
1079
|
paramsError: parseErrors[index],
|
|
1087
|
-
loaderPromise: Promise.resolve(),
|
|
1088
|
-
loadPromise,
|
|
1089
1080
|
routeContext: undefined!,
|
|
1090
1081
|
context: undefined!,
|
|
1091
1082
|
abortController: new AbortController(),
|
|
@@ -1097,6 +1088,7 @@ export class Router<
|
|
|
1097
1088
|
links: route.options.links?.(),
|
|
1098
1089
|
scripts: route.options.scripts?.(),
|
|
1099
1090
|
staticData: route.options.staticData || {},
|
|
1091
|
+
loadPromise: createControlledPromise(),
|
|
1100
1092
|
}
|
|
1101
1093
|
}
|
|
1102
1094
|
|
|
@@ -1134,7 +1126,7 @@ export class Router<
|
|
|
1134
1126
|
}
|
|
1135
1127
|
|
|
1136
1128
|
cancelMatch = (id: string) => {
|
|
1137
|
-
|
|
1129
|
+
this.getMatch(id)?.abortController.abort()
|
|
1138
1130
|
}
|
|
1139
1131
|
|
|
1140
1132
|
cancelMatches = () => {
|
|
@@ -1367,12 +1359,13 @@ export class Router<
|
|
|
1367
1359
|
return buildWithMatches(opts)
|
|
1368
1360
|
}
|
|
1369
1361
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1362
|
+
commitLocationPromise: undefined | ControlledPromise<void>
|
|
1363
|
+
|
|
1364
|
+
commitLocation = ({
|
|
1372
1365
|
viewTransition,
|
|
1373
1366
|
ignoreBlocker,
|
|
1374
1367
|
...next
|
|
1375
|
-
}: ParsedLocation & CommitLocationOptions) => {
|
|
1368
|
+
}: ParsedLocation & CommitLocationOptions): Promise<void> => {
|
|
1376
1369
|
const isSameState = () => {
|
|
1377
1370
|
// `state.key` is ignored but may still be provided when navigating,
|
|
1378
1371
|
// temporarily add the previous key to the next state so it doesn't affect
|
|
@@ -1386,6 +1379,11 @@ export class Router<
|
|
|
1386
1379
|
|
|
1387
1380
|
const isSameUrl = this.latestLocation.href === next.href
|
|
1388
1381
|
|
|
1382
|
+
const previousCommitPromise = this.commitLocationPromise
|
|
1383
|
+
this.commitLocationPromise = createControlledPromise<void>(() => {
|
|
1384
|
+
previousCommitPromise?.resolve()
|
|
1385
|
+
})
|
|
1386
|
+
|
|
1389
1387
|
// Don't commit to history if nothing changed
|
|
1390
1388
|
if (isSameUrl && isSameState()) {
|
|
1391
1389
|
this.load()
|
|
@@ -1432,13 +1430,16 @@ export class Router<
|
|
|
1432
1430
|
|
|
1433
1431
|
this.resetNextScroll = next.resetScroll ?? true
|
|
1434
1432
|
|
|
1435
|
-
|
|
1433
|
+
if (!this.history.subscribers.size) {
|
|
1434
|
+
this.load()
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
return this.commitLocationPromise
|
|
1436
1438
|
}
|
|
1437
1439
|
|
|
1438
1440
|
buildAndCommitLocation = ({
|
|
1439
1441
|
replace,
|
|
1440
1442
|
resetScroll,
|
|
1441
|
-
startTransition,
|
|
1442
1443
|
viewTransition,
|
|
1443
1444
|
ignoreBlocker,
|
|
1444
1445
|
...rest
|
|
@@ -1446,7 +1447,6 @@ export class Router<
|
|
|
1446
1447
|
const location = this.buildLocation(rest as any)
|
|
1447
1448
|
return this.commitLocation({
|
|
1448
1449
|
...location,
|
|
1449
|
-
startTransition,
|
|
1450
1450
|
viewTransition,
|
|
1451
1451
|
replace,
|
|
1452
1452
|
resetScroll,
|
|
@@ -1471,7 +1471,7 @@ export class Router<
|
|
|
1471
1471
|
|
|
1472
1472
|
invariant(
|
|
1473
1473
|
!isExternal,
|
|
1474
|
-
'Attempting to navigate to external url with
|
|
1474
|
+
'Attempting to navigate to external url with router.navigate!',
|
|
1475
1475
|
)
|
|
1476
1476
|
|
|
1477
1477
|
return this.buildAndCommitLocation({
|
|
@@ -1482,156 +1482,174 @@ export class Router<
|
|
|
1482
1482
|
})
|
|
1483
1483
|
}
|
|
1484
1484
|
|
|
1485
|
+
latestLoadPromise: undefined | Promise<void>
|
|
1486
|
+
|
|
1485
1487
|
load = async (): Promise<void> => {
|
|
1486
1488
|
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1487
1489
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1490
|
+
this.__store.setState((s) => ({
|
|
1491
|
+
...s,
|
|
1492
|
+
loadedAt: Date.now(),
|
|
1493
|
+
}))
|
|
1491
1494
|
|
|
1492
|
-
const promise = createControlledPromise<void>()
|
|
1493
|
-
this.latestLoadPromise = promise
|
|
1494
1495
|
let redirect: ResolvedRedirect | undefined
|
|
1495
1496
|
let notFound: NotFoundError | undefined
|
|
1496
1497
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1498
|
+
const loadPromise = new Promise<void>((resolve) => {
|
|
1499
|
+
this.startReactTransition(async () => {
|
|
1500
|
+
try {
|
|
1501
|
+
const next = this.latestLocation
|
|
1502
|
+
const prevLocation = this.state.resolvedLocation
|
|
1503
|
+
const pathDidChange = prevLocation.href !== next.href
|
|
1504
|
+
|
|
1505
|
+
// Cancel any pending matches
|
|
1506
|
+
this.cancelMatches()
|
|
1507
|
+
|
|
1508
|
+
let pendingMatches!: Array<AnyRouteMatch>
|
|
1509
|
+
|
|
1510
|
+
this.__store.batch(() => {
|
|
1511
|
+
// this call breaks a route context of destination route after a redirect
|
|
1512
|
+
// we should be fine not eagerly calling this since we call it later
|
|
1513
|
+
// this.cleanCache()
|
|
1514
|
+
|
|
1515
|
+
// Match the routes
|
|
1516
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search)
|
|
1517
|
+
|
|
1518
|
+
// Ingest the new matches
|
|
1519
|
+
this.__store.setState((s) => ({
|
|
1520
|
+
...s,
|
|
1521
|
+
status: 'pending',
|
|
1522
|
+
isLoading: true,
|
|
1523
|
+
location: next,
|
|
1524
|
+
pendingMatches,
|
|
1525
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1526
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1527
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1528
|
+
}),
|
|
1529
|
+
}))
|
|
1530
|
+
})
|
|
1515
1531
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1525
|
-
return !pendingMatches.find((e) => e.id === d.id)
|
|
1526
|
-
}),
|
|
1527
|
-
}))
|
|
1528
|
-
})
|
|
1532
|
+
if (!this.state.redirect) {
|
|
1533
|
+
this.emit({
|
|
1534
|
+
type: 'onBeforeNavigate',
|
|
1535
|
+
fromLocation: prevLocation,
|
|
1536
|
+
toLocation: next,
|
|
1537
|
+
pathChanged: pathDidChange,
|
|
1538
|
+
})
|
|
1539
|
+
}
|
|
1529
1540
|
|
|
1530
|
-
if (!this.state.redirect) {
|
|
1531
1541
|
this.emit({
|
|
1532
|
-
type: '
|
|
1542
|
+
type: 'onBeforeLoad',
|
|
1533
1543
|
fromLocation: prevLocation,
|
|
1534
1544
|
toLocation: next,
|
|
1535
1545
|
pathChanged: pathDidChange,
|
|
1536
1546
|
})
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
this.emit({
|
|
1540
|
-
type: 'onBeforeLoad',
|
|
1541
|
-
fromLocation: prevLocation,
|
|
1542
|
-
toLocation: next,
|
|
1543
|
-
pathChanged: pathDidChange,
|
|
1544
|
-
})
|
|
1545
|
-
|
|
1546
|
-
await this.loadMatches({
|
|
1547
|
-
matches: pendingMatches,
|
|
1548
|
-
location: next,
|
|
1549
|
-
checkLatest: () => this.checkLatest(promise),
|
|
1550
|
-
onReady: async () => {
|
|
1551
|
-
await this.startViewTransition(async () => {
|
|
1552
|
-
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1553
|
-
|
|
1554
|
-
// Commit the pending matches. If a previous match was
|
|
1555
|
-
// removed, place it in the cachedMatches
|
|
1556
|
-
let exitingMatches!: Array<AnyRouteMatch>
|
|
1557
|
-
let enteringMatches!: Array<AnyRouteMatch>
|
|
1558
|
-
let stayingMatches!: Array<AnyRouteMatch>
|
|
1559
|
-
|
|
1560
|
-
this.__store.batch(() => {
|
|
1561
|
-
this.__store.setState((s) => {
|
|
1562
|
-
const previousMatches = s.matches
|
|
1563
|
-
const newMatches = s.pendingMatches || s.matches
|
|
1564
|
-
|
|
1565
|
-
exitingMatches = previousMatches.filter(
|
|
1566
|
-
(match) => !newMatches.find((d) => d.id === match.id),
|
|
1567
|
-
)
|
|
1568
|
-
enteringMatches = newMatches.filter(
|
|
1569
|
-
(match) => !previousMatches.find((d) => d.id === match.id),
|
|
1570
|
-
)
|
|
1571
|
-
stayingMatches = previousMatches.filter((match) =>
|
|
1572
|
-
newMatches.find((d) => d.id === match.id),
|
|
1573
|
-
)
|
|
1574
1547
|
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1548
|
+
await this.loadMatches({
|
|
1549
|
+
matches: pendingMatches,
|
|
1550
|
+
location: next,
|
|
1551
|
+
// eslint-disable-next-line ts/require-await
|
|
1552
|
+
onReady: async () => {
|
|
1553
|
+
// eslint-disable-next-line ts/require-await
|
|
1554
|
+
this.startViewTransition(async () => {
|
|
1555
|
+
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1556
|
+
|
|
1557
|
+
// Commit the pending matches. If a previous match was
|
|
1558
|
+
// removed, place it in the cachedMatches
|
|
1559
|
+
let exitingMatches!: Array<AnyRouteMatch>
|
|
1560
|
+
let enteringMatches!: Array<AnyRouteMatch>
|
|
1561
|
+
let stayingMatches!: Array<AnyRouteMatch>
|
|
1562
|
+
|
|
1563
|
+
this.__store.batch(() => {
|
|
1564
|
+
this.__store.setState((s) => {
|
|
1565
|
+
const previousMatches = s.matches
|
|
1566
|
+
const newMatches = s.pendingMatches || s.matches
|
|
1567
|
+
|
|
1568
|
+
exitingMatches = previousMatches.filter(
|
|
1569
|
+
(match) => !newMatches.find((d) => d.id === match.id),
|
|
1570
|
+
)
|
|
1571
|
+
enteringMatches = newMatches.filter(
|
|
1572
|
+
(match) =>
|
|
1573
|
+
!previousMatches.find((d) => d.id === match.id),
|
|
1574
|
+
)
|
|
1575
|
+
stayingMatches = previousMatches.filter((match) =>
|
|
1576
|
+
newMatches.find((d) => d.id === match.id),
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
return {
|
|
1580
|
+
...s,
|
|
1581
|
+
isLoading: false,
|
|
1582
|
+
matches: newMatches,
|
|
1583
|
+
pendingMatches: undefined,
|
|
1584
|
+
cachedMatches: [
|
|
1585
|
+
...s.cachedMatches,
|
|
1586
|
+
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1587
|
+
],
|
|
1588
|
+
}
|
|
1589
|
+
})
|
|
1590
|
+
this.cleanCache()
|
|
1585
1591
|
})
|
|
1586
|
-
this.cleanCache()
|
|
1587
|
-
})
|
|
1588
1592
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1593
|
+
//
|
|
1594
|
+
;(
|
|
1595
|
+
[
|
|
1596
|
+
[exitingMatches, 'onLeave'],
|
|
1597
|
+
[enteringMatches, 'onEnter'],
|
|
1598
|
+
[stayingMatches, 'onStay'],
|
|
1599
|
+
] as const
|
|
1600
|
+
).forEach(([matches, hook]) => {
|
|
1601
|
+
matches.forEach((match) => {
|
|
1602
|
+
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1603
|
+
})
|
|
1599
1604
|
})
|
|
1600
1605
|
})
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
})
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1606
|
+
},
|
|
1607
|
+
})
|
|
1608
|
+
} catch (err) {
|
|
1609
|
+
if (isResolvedRedirect(err)) {
|
|
1610
|
+
redirect = err
|
|
1611
|
+
if (!this.isServer) {
|
|
1612
|
+
this.navigate({ ...err, replace: true, __isRedirect: true })
|
|
1613
|
+
}
|
|
1614
|
+
} else if (isNotFound(err)) {
|
|
1615
|
+
notFound = err
|
|
1610
1616
|
}
|
|
1611
|
-
} else if (isNotFound(err)) {
|
|
1612
|
-
notFound = err
|
|
1613
|
-
}
|
|
1614
1617
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1618
|
+
this.__store.setState((s) => ({
|
|
1619
|
+
...s,
|
|
1620
|
+
statusCode: redirect
|
|
1621
|
+
? redirect.statusCode
|
|
1622
|
+
: notFound
|
|
1623
|
+
? 404
|
|
1624
|
+
: s.matches.some((d) => d.status === 'error')
|
|
1625
|
+
? 500
|
|
1626
|
+
: 200,
|
|
1627
|
+
redirect,
|
|
1628
|
+
}))
|
|
1629
|
+
}
|
|
1627
1630
|
|
|
1628
|
-
|
|
1631
|
+
if (this.latestLoadPromise === loadPromise) {
|
|
1632
|
+
this.commitLocationPromise?.resolve()
|
|
1633
|
+
this.latestLoadPromise = undefined
|
|
1634
|
+
this.commitLocationPromise = undefined
|
|
1635
|
+
}
|
|
1636
|
+
resolve()
|
|
1637
|
+
})
|
|
1629
1638
|
})
|
|
1630
1639
|
|
|
1631
|
-
|
|
1640
|
+
this.latestLoadPromise = loadPromise
|
|
1641
|
+
|
|
1642
|
+
await loadPromise
|
|
1643
|
+
|
|
1644
|
+
while (
|
|
1645
|
+
(this.latestLoadPromise as any) &&
|
|
1646
|
+
loadPromise !== this.latestLoadPromise
|
|
1647
|
+
) {
|
|
1648
|
+
await this.latestLoadPromise
|
|
1649
|
+
}
|
|
1632
1650
|
}
|
|
1633
1651
|
|
|
1634
|
-
startViewTransition =
|
|
1652
|
+
startViewTransition = (fn: () => Promise<void>) => {
|
|
1635
1653
|
// Determine if we should start a view transition from the navigation
|
|
1636
1654
|
// or from the router default
|
|
1637
1655
|
const shouldViewTransition =
|
|
@@ -1648,18 +1666,54 @@ export class Router<
|
|
|
1648
1666
|
?.startViewTransition?.(fn) || fn()
|
|
1649
1667
|
}
|
|
1650
1668
|
|
|
1669
|
+
updateMatch = (
|
|
1670
|
+
id: string,
|
|
1671
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
1672
|
+
) => {
|
|
1673
|
+
let updated!: AnyRouteMatch
|
|
1674
|
+
const isPending = this.state.pendingMatches?.find((d) => d.id === id)
|
|
1675
|
+
const isMatched = this.state.matches.find((d) => d.id === id)
|
|
1676
|
+
|
|
1677
|
+
const matchesKey = isPending
|
|
1678
|
+
? 'pendingMatches'
|
|
1679
|
+
: isMatched
|
|
1680
|
+
? 'matches'
|
|
1681
|
+
: 'cachedMatches'
|
|
1682
|
+
|
|
1683
|
+
this.__store.setState((s) => ({
|
|
1684
|
+
...s,
|
|
1685
|
+
[matchesKey]: s[matchesKey]?.map((d) =>
|
|
1686
|
+
d.id === id ? (updated = updater(d)) : d,
|
|
1687
|
+
),
|
|
1688
|
+
}))
|
|
1689
|
+
|
|
1690
|
+
return updated
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
getMatch = (matchId: string) => {
|
|
1694
|
+
return [
|
|
1695
|
+
...this.state.cachedMatches,
|
|
1696
|
+
...(this.state.pendingMatches ?? []),
|
|
1697
|
+
...this.state.matches,
|
|
1698
|
+
].find((d) => d.id === matchId)
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1651
1701
|
loadMatches = async ({
|
|
1652
|
-
checkLatest,
|
|
1653
1702
|
location,
|
|
1654
1703
|
matches,
|
|
1655
1704
|
preload,
|
|
1656
1705
|
onReady,
|
|
1706
|
+
updateMatch = this.updateMatch,
|
|
1657
1707
|
}: {
|
|
1658
|
-
checkLatest: () => void
|
|
1659
1708
|
location: ParsedLocation
|
|
1660
1709
|
matches: Array<AnyRouteMatch>
|
|
1661
1710
|
preload?: boolean
|
|
1662
1711
|
onReady?: () => Promise<void>
|
|
1712
|
+
updateMatch?: (
|
|
1713
|
+
id: string,
|
|
1714
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
1715
|
+
) => void
|
|
1716
|
+
getMatch?: (matchId: string) => AnyRouteMatch | undefined
|
|
1663
1717
|
}): Promise<Array<MakeRouteMatch>> => {
|
|
1664
1718
|
let firstBadMatchIndex: number | undefined
|
|
1665
1719
|
let rendered = false
|
|
@@ -1675,38 +1729,10 @@ export class Router<
|
|
|
1675
1729
|
triggerOnReady()
|
|
1676
1730
|
}
|
|
1677
1731
|
|
|
1678
|
-
const updateMatch = (
|
|
1679
|
-
id: string,
|
|
1680
|
-
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
1681
|
-
opts?: { remove?: boolean },
|
|
1682
|
-
) => {
|
|
1683
|
-
let updated!: AnyRouteMatch
|
|
1684
|
-
const isPending = this.state.pendingMatches?.find((d) => d.id === id)
|
|
1685
|
-
const isMatched = this.state.matches.find((d) => d.id === id)
|
|
1686
|
-
|
|
1687
|
-
const matchesKey = isPending
|
|
1688
|
-
? 'pendingMatches'
|
|
1689
|
-
: isMatched
|
|
1690
|
-
? 'matches'
|
|
1691
|
-
: 'cachedMatches'
|
|
1692
|
-
|
|
1693
|
-
this.__store.setState((s) => ({
|
|
1694
|
-
...s,
|
|
1695
|
-
[matchesKey]: opts?.remove
|
|
1696
|
-
? s[matchesKey]?.filter((d) => d.id !== id)
|
|
1697
|
-
: s[matchesKey]?.map((d) =>
|
|
1698
|
-
d.id === id ? (updated = updater(d)) : d,
|
|
1699
|
-
),
|
|
1700
|
-
}))
|
|
1701
|
-
|
|
1702
|
-
return updated
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
1732
|
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
1706
1733
|
if (isResolvedRedirect(err)) throw err
|
|
1707
1734
|
|
|
1708
1735
|
if (isRedirect(err) || isNotFound(err)) {
|
|
1709
|
-
// if (!rendered) {
|
|
1710
1736
|
updateMatch(match.id, (prev) => ({
|
|
1711
1737
|
...prev,
|
|
1712
1738
|
status: isRedirect(err)
|
|
@@ -1716,19 +1742,26 @@ export class Router<
|
|
|
1716
1742
|
: 'error',
|
|
1717
1743
|
isFetching: false,
|
|
1718
1744
|
error: err,
|
|
1745
|
+
beforeLoadPromise: undefined,
|
|
1746
|
+
loaderPromise: undefined,
|
|
1719
1747
|
}))
|
|
1720
|
-
// }
|
|
1721
1748
|
|
|
1722
1749
|
if (!(err as any).routeId) {
|
|
1723
1750
|
;(err as any).routeId = match.routeId
|
|
1724
1751
|
}
|
|
1725
1752
|
|
|
1753
|
+
match.beforeLoadPromise?.resolve()
|
|
1754
|
+
match.loaderPromise?.resolve()
|
|
1755
|
+
match.loadPromise?.resolve()
|
|
1756
|
+
|
|
1726
1757
|
if (isRedirect(err)) {
|
|
1727
1758
|
rendered = true
|
|
1728
1759
|
err = this.resolveRedirect({ ...err, _fromLocation: location })
|
|
1729
1760
|
throw err
|
|
1730
1761
|
} else if (isNotFound(err)) {
|
|
1731
|
-
this.
|
|
1762
|
+
this._handleNotFound(matches, err, {
|
|
1763
|
+
updateMatch,
|
|
1764
|
+
})
|
|
1732
1765
|
throw err
|
|
1733
1766
|
}
|
|
1734
1767
|
}
|
|
@@ -1738,397 +1771,426 @@ export class Router<
|
|
|
1738
1771
|
await new Promise<void>((resolveAll, rejectAll) => {
|
|
1739
1772
|
;(async () => {
|
|
1740
1773
|
try {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
!this.isServer &&
|
|
1755
|
-
!preload &&
|
|
1756
|
-
(route.options.loader || route.options.beforeLoad) &&
|
|
1757
|
-
typeof pendingMs === 'number' &&
|
|
1758
|
-
pendingMs !== Infinity &&
|
|
1759
|
-
(route.options.pendingComponent ??
|
|
1760
|
-
this.options.defaultPendingComponent)
|
|
1761
|
-
)
|
|
1762
|
-
|
|
1763
|
-
if (shouldPending) {
|
|
1764
|
-
// If we might show a pending component, we need to wait for the
|
|
1765
|
-
// pending promise to resolve before we start showing that state
|
|
1766
|
-
setTimeout(() => {
|
|
1767
|
-
try {
|
|
1768
|
-
checkLatest()
|
|
1769
|
-
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1770
|
-
// the pending component can start rendering
|
|
1771
|
-
triggerOnReady()
|
|
1772
|
-
} catch {}
|
|
1773
|
-
}, pendingMs)
|
|
1774
|
+
const handleSerialError = (
|
|
1775
|
+
index: number,
|
|
1776
|
+
err: any,
|
|
1777
|
+
routerCode: string,
|
|
1778
|
+
) => {
|
|
1779
|
+
const { id: matchId, routeId } = matches[index]!
|
|
1780
|
+
const route = this.looseRoutesById[routeId]!
|
|
1781
|
+
|
|
1782
|
+
// Much like suspense, we use a promise here to know if
|
|
1783
|
+
// we've been outdated by a new loadMatches call and
|
|
1784
|
+
// should abort the current async operation
|
|
1785
|
+
if (err instanceof Promise) {
|
|
1786
|
+
throw err
|
|
1774
1787
|
}
|
|
1775
1788
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1789
|
+
err.routerCode = routerCode
|
|
1790
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
1791
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
1779
1792
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
// Otherwise, load the route
|
|
1788
|
-
matches[index] = match = updateMatch(match.id, (prev) => ({
|
|
1789
|
-
...prev,
|
|
1790
|
-
isFetching: 'beforeLoad',
|
|
1791
|
-
loadPromise,
|
|
1792
|
-
}))
|
|
1793
|
-
|
|
1794
|
-
const handleSerialError = (err: any, routerCode: string) => {
|
|
1795
|
-
// If the error is a promise, it means we're outdated and
|
|
1796
|
-
// should abort the current async operation
|
|
1797
|
-
if (err instanceof Promise) {
|
|
1798
|
-
throw err
|
|
1799
|
-
}
|
|
1793
|
+
try {
|
|
1794
|
+
route.options.onError?.(err)
|
|
1795
|
+
} catch (errorHandlerErr) {
|
|
1796
|
+
err = errorHandlerErr
|
|
1797
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
1798
|
+
}
|
|
1800
1799
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
handleRedirectAndNotFound(match, err)
|
|
1800
|
+
updateMatch(matchId, (prev) => {
|
|
1801
|
+
prev.beforeLoadPromise?.resolve()
|
|
1804
1802
|
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
} catch (errorHandlerErr) {
|
|
1808
|
-
err = errorHandlerErr
|
|
1809
|
-
handleRedirectAndNotFound(match, err)
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
matches[index] = match = updateMatch(match.id, () => ({
|
|
1813
|
-
...match,
|
|
1803
|
+
return {
|
|
1804
|
+
...prev,
|
|
1814
1805
|
error: err,
|
|
1815
1806
|
status: 'error',
|
|
1807
|
+
isFetching: false,
|
|
1816
1808
|
updatedAt: Date.now(),
|
|
1817
1809
|
abortController: new AbortController(),
|
|
1818
|
-
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
if (match.paramsError) {
|
|
1822
|
-
handleSerialError(match.paramsError, 'PARSE_PARAMS')
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
if (match.searchError) {
|
|
1826
|
-
handleSerialError(match.searchError, 'VALIDATE_SEARCH')
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
try {
|
|
1830
|
-
const parentContext =
|
|
1831
|
-
parentMatch?.context ?? this.options.context ?? {}
|
|
1832
|
-
|
|
1833
|
-
// Make sure the match has parent context set before going further
|
|
1834
|
-
matches[index] = match = {
|
|
1835
|
-
...match,
|
|
1836
|
-
routeContext: replaceEqualDeep(
|
|
1837
|
-
match.routeContext,
|
|
1838
|
-
parentContext,
|
|
1839
|
-
),
|
|
1840
|
-
context: replaceEqualDeep(match.context, parentContext),
|
|
1841
|
-
abortController,
|
|
1810
|
+
beforeLoadPromise: undefined,
|
|
1842
1811
|
}
|
|
1812
|
+
})
|
|
1813
|
+
}
|
|
1843
1814
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1815
|
+
for (const [index, { id: matchId, routeId }] of matches.entries()) {
|
|
1816
|
+
const existingMatch = this.getMatch(matchId)!
|
|
1817
|
+
|
|
1818
|
+
if (
|
|
1819
|
+
// If we are in the middle of a load, either of these will be present
|
|
1820
|
+
// (not to be confused with `loadPromise`, which is always defined)
|
|
1821
|
+
existingMatch.beforeLoadPromise ||
|
|
1822
|
+
existingMatch.loaderPromise
|
|
1823
|
+
) {
|
|
1824
|
+
// Wait for the beforeLoad to resolve before we continue
|
|
1825
|
+
await existingMatch.beforeLoadPromise
|
|
1826
|
+
} else {
|
|
1827
|
+
// If we are not in the middle of a load, start it
|
|
1828
|
+
try {
|
|
1829
|
+
updateMatch(matchId, (prev) => ({
|
|
1830
|
+
...prev,
|
|
1831
|
+
loadPromise: createControlledPromise<void>(() => {
|
|
1832
|
+
prev.loadPromise?.resolve()
|
|
1833
|
+
}),
|
|
1834
|
+
beforeLoadPromise: createControlledPromise<void>(),
|
|
1835
|
+
}))
|
|
1836
|
+
|
|
1837
|
+
const route = this.looseRoutesById[routeId]!
|
|
1838
|
+
const abortController = new AbortController()
|
|
1839
|
+
|
|
1840
|
+
const parentMatchId = matches[index - 1]?.id
|
|
1841
|
+
|
|
1842
|
+
const getParentContext = () => {
|
|
1843
|
+
if (!parentMatchId) {
|
|
1844
|
+
return (this.options.context as any) ?? {}
|
|
1845
|
+
}
|
|
1856
1846
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1847
|
+
return (
|
|
1848
|
+
this.getMatch(parentMatchId)!.context ??
|
|
1849
|
+
this.options.context ??
|
|
1850
|
+
{}
|
|
1851
|
+
)
|
|
1852
|
+
}
|
|
1861
1853
|
|
|
1862
|
-
|
|
1854
|
+
const pendingMs =
|
|
1855
|
+
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1856
|
+
|
|
1857
|
+
const shouldPending = !!(
|
|
1858
|
+
onReady &&
|
|
1859
|
+
!this.isServer &&
|
|
1860
|
+
!preload &&
|
|
1861
|
+
(route.options.loader || route.options.beforeLoad) &&
|
|
1862
|
+
typeof pendingMs === 'number' &&
|
|
1863
|
+
pendingMs !== Infinity &&
|
|
1864
|
+
(route.options.pendingComponent ??
|
|
1865
|
+
this.options.defaultPendingComponent)
|
|
1866
|
+
)
|
|
1863
1867
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1868
|
+
if (shouldPending) {
|
|
1869
|
+
// If we might show a pending component, we need to wait for the
|
|
1870
|
+
// pending promise to resolve before we start showing that state
|
|
1871
|
+
setTimeout(() => {
|
|
1872
|
+
try {
|
|
1873
|
+
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1874
|
+
// the pending component can start rendering
|
|
1875
|
+
triggerOnReady()
|
|
1876
|
+
} catch {}
|
|
1877
|
+
}, pendingMs)
|
|
1878
|
+
}
|
|
1870
1879
|
|
|
1871
|
-
|
|
1872
|
-
...parentContext,
|
|
1873
|
-
...beforeLoadContext,
|
|
1874
|
-
}
|
|
1880
|
+
const { paramsError, searchError } = this.getMatch(matchId)!
|
|
1875
1881
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
match.routeContext,
|
|
1880
|
-
beforeLoadContext,
|
|
1881
|
-
),
|
|
1882
|
-
context: replaceEqualDeep(match.context, context),
|
|
1883
|
-
abortController,
|
|
1884
|
-
}
|
|
1885
|
-
updateMatch(match.id, () => match)
|
|
1886
|
-
} catch (err) {
|
|
1887
|
-
handleSerialError(err, 'BEFORE_LOAD')
|
|
1888
|
-
break
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1882
|
+
if (paramsError) {
|
|
1883
|
+
handleSerialError(index, paramsError, 'PARSE_PARAMS')
|
|
1884
|
+
}
|
|
1891
1885
|
|
|
1892
|
-
|
|
1886
|
+
if (searchError) {
|
|
1887
|
+
handleSerialError(index, searchError, 'VALIDATE_SEARCH')
|
|
1888
|
+
}
|
|
1893
1889
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1890
|
+
const parentContext = getParentContext()
|
|
1891
|
+
|
|
1892
|
+
updateMatch(matchId, (prev) => ({
|
|
1893
|
+
...prev,
|
|
1894
|
+
isFetching: 'beforeLoad',
|
|
1895
|
+
fetchCount: prev.fetchCount + 1,
|
|
1896
|
+
routeContext: replaceEqualDeep(
|
|
1897
|
+
prev.routeContext,
|
|
1898
|
+
parentContext,
|
|
1899
|
+
),
|
|
1900
|
+
context: replaceEqualDeep(prev.context, parentContext),
|
|
1901
|
+
abortController,
|
|
1902
|
+
}))
|
|
1903
|
+
|
|
1904
|
+
const { search, params, routeContext, cause } =
|
|
1905
|
+
this.getMatch(matchId)!
|
|
1906
|
+
|
|
1907
|
+
const beforeLoadFnContext = {
|
|
1908
|
+
search,
|
|
1909
|
+
abortController,
|
|
1910
|
+
params,
|
|
1911
|
+
preload: !!preload,
|
|
1912
|
+
context: routeContext,
|
|
1913
|
+
location,
|
|
1914
|
+
navigate: (opts: any) =>
|
|
1915
|
+
this.navigate({ ...opts, _fromLocation: location }),
|
|
1916
|
+
buildLocation: this.buildLocation,
|
|
1917
|
+
cause: preload ? 'preload' : cause,
|
|
1918
|
+
}
|
|
1896
1919
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
const route = this.looseRoutesById[match.routeId]!
|
|
1901
|
-
|
|
1902
|
-
const loaderContext: LoaderFnContext = {
|
|
1903
|
-
params: match.params,
|
|
1904
|
-
deps: match.loaderDeps,
|
|
1905
|
-
preload: !!preload,
|
|
1906
|
-
parentMatchPromise,
|
|
1907
|
-
abortController: match.abortController,
|
|
1908
|
-
context: match.context,
|
|
1909
|
-
location,
|
|
1910
|
-
navigate: (opts) =>
|
|
1911
|
-
this.navigate({ ...opts, _fromLocation: location }),
|
|
1912
|
-
cause: preload ? 'preload' : match.cause,
|
|
1913
|
-
route,
|
|
1914
|
-
}
|
|
1920
|
+
const beforeLoadContext =
|
|
1921
|
+
(await route.options.beforeLoad?.(beforeLoadFnContext)) ??
|
|
1922
|
+
{}
|
|
1915
1923
|
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
// If the Matches component rendered
|
|
1923
|
-
// the pending component and needs to show it for
|
|
1924
|
-
// a minimum duration, we''ll wait for it to resolve
|
|
1925
|
-
// before committing to the match and resolving
|
|
1926
|
-
// the loadPromise
|
|
1927
|
-
const potentialPendingMinPromise = async () => {
|
|
1928
|
-
const latestMatch = getRouteMatch(this.state, match.id)
|
|
1929
|
-
|
|
1930
|
-
if (latestMatch?.minPendingPromise) {
|
|
1931
|
-
await latestMatch.minPendingPromise
|
|
1932
|
-
|
|
1933
|
-
checkLatest()
|
|
1934
|
-
|
|
1935
|
-
updateMatch(latestMatch.id, (prev) => ({
|
|
1936
|
-
...prev,
|
|
1937
|
-
minPendingPromise: undefined,
|
|
1938
|
-
}))
|
|
1939
|
-
}
|
|
1924
|
+
if (
|
|
1925
|
+
isRedirect(beforeLoadContext) ||
|
|
1926
|
+
isNotFound(beforeLoadContext)
|
|
1927
|
+
) {
|
|
1928
|
+
handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD')
|
|
1940
1929
|
}
|
|
1941
1930
|
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
// if (match.fetchCount && match.status === 'success') {
|
|
1948
|
-
// resolve()
|
|
1949
|
-
// }
|
|
1950
|
-
|
|
1951
|
-
// Otherwise, load the route
|
|
1952
|
-
matches[index] = match = updateMatch(
|
|
1953
|
-
match.id,
|
|
1954
|
-
(prev) => ({
|
|
1955
|
-
...prev,
|
|
1956
|
-
isFetching: 'loader',
|
|
1957
|
-
fetchCount: match.fetchCount + 1,
|
|
1958
|
-
}),
|
|
1959
|
-
)
|
|
1960
|
-
|
|
1961
|
-
lazyPromise =
|
|
1962
|
-
route.lazyFn?.().then((lazyRoute) => {
|
|
1963
|
-
Object.assign(route.options, lazyRoute.options)
|
|
1964
|
-
}) || Promise.resolve()
|
|
1965
|
-
|
|
1966
|
-
// If for some reason lazy resolves more lazy components...
|
|
1967
|
-
// We'll wait for that before pre attempt to preload any
|
|
1968
|
-
// components themselves.
|
|
1969
|
-
componentsPromise = lazyPromise.then(() =>
|
|
1970
|
-
Promise.all(
|
|
1971
|
-
componentTypes.map(async (type) => {
|
|
1972
|
-
const component = route.options[type]
|
|
1973
|
-
|
|
1974
|
-
if ((component as any)?.preload) {
|
|
1975
|
-
await (component as any).preload()
|
|
1976
|
-
}
|
|
1977
|
-
}),
|
|
1978
|
-
),
|
|
1979
|
-
)
|
|
1980
|
-
|
|
1981
|
-
// Lazy option can modify the route options,
|
|
1982
|
-
// so we need to wait for it to resolve before
|
|
1983
|
-
// we can use the options
|
|
1984
|
-
await lazyPromise
|
|
1985
|
-
|
|
1986
|
-
checkLatest()
|
|
1987
|
-
|
|
1988
|
-
// Kick off the loader!
|
|
1989
|
-
loaderPromise = route.options.loader?.(loaderContext)
|
|
1990
|
-
|
|
1991
|
-
matches[index] = match = updateMatch(
|
|
1992
|
-
match.id,
|
|
1993
|
-
(prev) => ({
|
|
1994
|
-
...prev,
|
|
1995
|
-
loaderPromise,
|
|
1996
|
-
}),
|
|
1997
|
-
)
|
|
1931
|
+
updateMatch(matchId, (prev) => {
|
|
1932
|
+
const routeContext = {
|
|
1933
|
+
...prev.routeContext,
|
|
1934
|
+
...beforeLoadContext,
|
|
1998
1935
|
}
|
|
1999
1936
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1937
|
+
return {
|
|
1938
|
+
...prev,
|
|
1939
|
+
routeContext: replaceEqualDeep(
|
|
1940
|
+
prev.routeContext,
|
|
1941
|
+
routeContext,
|
|
1942
|
+
),
|
|
1943
|
+
context: replaceEqualDeep(prev.context, routeContext),
|
|
1944
|
+
abortController,
|
|
2006
1945
|
}
|
|
2007
|
-
|
|
1946
|
+
})
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
handleSerialError(index, err, 'BEFORE_LOAD')
|
|
1949
|
+
}
|
|
2008
1950
|
|
|
2009
|
-
|
|
1951
|
+
updateMatch(matchId, (prev) => {
|
|
1952
|
+
prev.beforeLoadPromise?.resolve()
|
|
2010
1953
|
|
|
2011
|
-
|
|
2012
|
-
|
|
1954
|
+
return {
|
|
1955
|
+
...prev,
|
|
1956
|
+
beforeLoadPromise: undefined,
|
|
1957
|
+
isFetching: false,
|
|
1958
|
+
}
|
|
1959
|
+
})
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
2013
1962
|
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
match,
|
|
2017
|
-
params: match.params,
|
|
2018
|
-
loaderData,
|
|
2019
|
-
})
|
|
1963
|
+
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
1964
|
+
const matchPromises: Array<Promise<any>> = []
|
|
2020
1965
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
1966
|
+
validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
|
|
1967
|
+
matchPromises.push(
|
|
1968
|
+
(async () => {
|
|
1969
|
+
const { loaderPromise: prevLoaderPromise } =
|
|
1970
|
+
this.getMatch(matchId)!
|
|
1971
|
+
|
|
1972
|
+
if (prevLoaderPromise) {
|
|
1973
|
+
await prevLoaderPromise
|
|
1974
|
+
} else {
|
|
1975
|
+
const parentMatchPromise = matchPromises[index - 1]
|
|
1976
|
+
const route = this.looseRoutesById[routeId]!
|
|
1977
|
+
|
|
1978
|
+
const getLoaderContext = (): LoaderFnContext => {
|
|
1979
|
+
const {
|
|
1980
|
+
params,
|
|
1981
|
+
loaderDeps,
|
|
1982
|
+
abortController,
|
|
1983
|
+
context,
|
|
1984
|
+
cause,
|
|
1985
|
+
} = this.getMatch(matchId)!
|
|
1986
|
+
|
|
1987
|
+
return {
|
|
1988
|
+
params,
|
|
1989
|
+
deps: loaderDeps,
|
|
1990
|
+
preload: !!preload,
|
|
1991
|
+
parentMatchPromise,
|
|
1992
|
+
abortController: abortController,
|
|
1993
|
+
context,
|
|
1994
|
+
location,
|
|
1995
|
+
navigate: (opts) =>
|
|
1996
|
+
this.navigate({ ...opts, _fromLocation: location }),
|
|
1997
|
+
cause: preload ? 'preload' : cause,
|
|
1998
|
+
route,
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2024
2001
|
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
error: undefined,
|
|
2028
|
-
status: 'success',
|
|
2029
|
-
isFetching: false,
|
|
2030
|
-
updatedAt: Date.now(),
|
|
2031
|
-
loaderData,
|
|
2032
|
-
meta,
|
|
2033
|
-
headers,
|
|
2034
|
-
}))
|
|
2035
|
-
} catch (e) {
|
|
2036
|
-
checkLatest()
|
|
2037
|
-
let error = e
|
|
2002
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
2003
|
+
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2038
2004
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2005
|
+
const staleAge = preload
|
|
2006
|
+
? (route.options.preloadStaleTime ??
|
|
2007
|
+
this.options.defaultPreloadStaleTime ??
|
|
2008
|
+
30_000) // 30 seconds for preloads by default
|
|
2009
|
+
: (route.options.staleTime ??
|
|
2010
|
+
this.options.defaultStaleTime ??
|
|
2011
|
+
0)
|
|
2041
2012
|
|
|
2042
|
-
|
|
2013
|
+
const shouldReloadOption = route.options.shouldReload
|
|
2043
2014
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2015
|
+
// Default to reloading the route all the time
|
|
2016
|
+
// Allow shouldReload to get the last say,
|
|
2017
|
+
// if provided.
|
|
2018
|
+
const shouldReload =
|
|
2019
|
+
typeof shouldReloadOption === 'function'
|
|
2020
|
+
? shouldReloadOption(getLoaderContext())
|
|
2021
|
+
: shouldReloadOption
|
|
2050
2022
|
|
|
2051
|
-
|
|
2023
|
+
updateMatch(matchId, (prev) => ({
|
|
2052
2024
|
...prev,
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2025
|
+
loaderPromise: createControlledPromise<void>(),
|
|
2026
|
+
preload:
|
|
2027
|
+
!!preload &&
|
|
2028
|
+
!this.state.matches.find((d) => d.id === matchId),
|
|
2056
2029
|
}))
|
|
2057
|
-
}
|
|
2058
2030
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2031
|
+
const runLoader = async () => {
|
|
2032
|
+
try {
|
|
2033
|
+
// If the Matches component rendered
|
|
2034
|
+
// the pending component and needs to show it for
|
|
2035
|
+
// a minimum duration, we''ll wait for it to resolve
|
|
2036
|
+
// before committing to the match and resolving
|
|
2037
|
+
// the loadPromise
|
|
2038
|
+
const potentialPendingMinPromise = async () => {
|
|
2039
|
+
const latestMatch = this.getMatch(matchId)!
|
|
2040
|
+
|
|
2041
|
+
if (latestMatch.minPendingPromise) {
|
|
2042
|
+
await latestMatch.minPendingPromise
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// Actually run the loader and handle the result
|
|
2047
|
+
try {
|
|
2048
|
+
route._lazyPromise =
|
|
2049
|
+
route._lazyPromise ||
|
|
2050
|
+
(route.lazyFn
|
|
2051
|
+
? route.lazyFn().then((lazyRoute) => {
|
|
2052
|
+
Object.assign(
|
|
2053
|
+
route.options,
|
|
2054
|
+
lazyRoute.options,
|
|
2055
|
+
)
|
|
2056
|
+
})
|
|
2057
|
+
: Promise.resolve())
|
|
2058
|
+
|
|
2059
|
+
// If for some reason lazy resolves more lazy components...
|
|
2060
|
+
// We'll wait for that before pre attempt to preload any
|
|
2061
|
+
// components themselves.
|
|
2062
|
+
const componentsPromise =
|
|
2063
|
+
this.getMatch(matchId)!.componentsPromise ||
|
|
2064
|
+
route._lazyPromise.then(() =>
|
|
2065
|
+
Promise.all(
|
|
2066
|
+
componentTypes.map(async (type) => {
|
|
2067
|
+
const component = route.options[type]
|
|
2068
|
+
|
|
2069
|
+
if ((component as any)?.preload) {
|
|
2070
|
+
await (component as any).preload()
|
|
2071
|
+
}
|
|
2072
|
+
}),
|
|
2073
|
+
),
|
|
2074
|
+
)
|
|
2075
|
+
|
|
2076
|
+
// Otherwise, load the route
|
|
2077
|
+
updateMatch(matchId, (prev) => ({
|
|
2078
|
+
...prev,
|
|
2079
|
+
isFetching: 'loader',
|
|
2080
|
+
componentsPromise,
|
|
2081
|
+
}))
|
|
2082
|
+
|
|
2083
|
+
// Lazy option can modify the route options,
|
|
2084
|
+
// so we need to wait for it to resolve before
|
|
2085
|
+
// we can use the options
|
|
2086
|
+
await route._lazyPromise
|
|
2087
|
+
|
|
2088
|
+
// Kick off the loader!
|
|
2089
|
+
let loaderData =
|
|
2090
|
+
await route.options.loader?.(getLoaderContext())
|
|
2091
|
+
|
|
2092
|
+
if (this.serializeLoaderData) {
|
|
2093
|
+
loaderData = this.serializeLoaderData(loaderData, {
|
|
2094
|
+
router: this,
|
|
2095
|
+
match: this.getMatch(matchId)!,
|
|
2096
|
+
})
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
handleRedirectAndNotFound(
|
|
2100
|
+
this.getMatch(matchId)!,
|
|
2101
|
+
loaderData,
|
|
2102
|
+
)
|
|
2103
|
+
|
|
2104
|
+
await potentialPendingMinPromise()
|
|
2105
|
+
|
|
2106
|
+
const meta = route.options.meta?.({
|
|
2107
|
+
matches,
|
|
2108
|
+
match: this.getMatch(matchId)!,
|
|
2109
|
+
params: this.getMatch(matchId)!.params,
|
|
2110
|
+
loaderData,
|
|
2111
|
+
})
|
|
2112
|
+
|
|
2113
|
+
const headers = route.options.headers?.({
|
|
2114
|
+
loaderData,
|
|
2115
|
+
})
|
|
2116
|
+
|
|
2117
|
+
updateMatch(matchId, (prev) => ({
|
|
2118
|
+
...prev,
|
|
2119
|
+
error: undefined,
|
|
2120
|
+
status: 'success',
|
|
2121
|
+
isFetching: false,
|
|
2122
|
+
updatedAt: Date.now(),
|
|
2123
|
+
loaderData,
|
|
2124
|
+
meta,
|
|
2125
|
+
headers,
|
|
2126
|
+
}))
|
|
2127
|
+
} catch (e) {
|
|
2128
|
+
let error = e
|
|
2129
|
+
|
|
2130
|
+
await potentialPendingMinPromise()
|
|
2131
|
+
|
|
2132
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, e)
|
|
2133
|
+
|
|
2134
|
+
try {
|
|
2135
|
+
route.options.onError?.(e)
|
|
2136
|
+
} catch (onErrorError) {
|
|
2137
|
+
error = onErrorError
|
|
2138
|
+
handleRedirectAndNotFound(
|
|
2139
|
+
this.getMatch(matchId)!,
|
|
2140
|
+
onErrorError,
|
|
2141
|
+
)
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
updateMatch(matchId, (prev) => ({
|
|
2145
|
+
...prev,
|
|
2146
|
+
error,
|
|
2147
|
+
status: 'error',
|
|
2148
|
+
isFetching: false,
|
|
2149
|
+
}))
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// Last but not least, wait for the the component
|
|
2153
|
+
// to be preloaded before we resolve the match
|
|
2154
|
+
await this.getMatch(matchId)!.componentsPromise
|
|
2155
|
+
} catch (err) {
|
|
2156
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2064
2159
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2160
|
+
// If the route is successful and still fresh, just resolve
|
|
2161
|
+
const { status, invalid } = this.getMatch(matchId)!
|
|
2162
|
+
|
|
2163
|
+
if (
|
|
2164
|
+
status === 'success' &&
|
|
2165
|
+
(invalid || (shouldReload ?? age > staleAge))
|
|
2166
|
+
) {
|
|
2167
|
+
;(async () => {
|
|
2168
|
+
try {
|
|
2169
|
+
await runLoader()
|
|
2170
|
+
} catch (err) {}
|
|
2171
|
+
})()
|
|
2172
|
+
} else if (status !== 'success') {
|
|
2173
|
+
await runLoader()
|
|
2174
|
+
}
|
|
2067
2175
|
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
const staleAge = preload
|
|
2072
|
-
? (route.options.preloadStaleTime ??
|
|
2073
|
-
this.options.defaultPreloadStaleTime ??
|
|
2074
|
-
30_000) // 30 seconds for preloads by default
|
|
2075
|
-
: (route.options.staleTime ??
|
|
2076
|
-
this.options.defaultStaleTime ??
|
|
2077
|
-
0)
|
|
2078
|
-
|
|
2079
|
-
const shouldReloadOption = route.options.shouldReload
|
|
2080
|
-
|
|
2081
|
-
// Default to reloading the route all the time
|
|
2082
|
-
// Allow shouldReload to get the last say,
|
|
2083
|
-
// if provided.
|
|
2084
|
-
const shouldReload =
|
|
2085
|
-
typeof shouldReloadOption === 'function'
|
|
2086
|
-
? shouldReloadOption(loaderContext)
|
|
2087
|
-
: shouldReloadOption
|
|
2088
|
-
|
|
2089
|
-
matches[index] = match = {
|
|
2090
|
-
...match,
|
|
2091
|
-
preload:
|
|
2092
|
-
!!preload &&
|
|
2093
|
-
!this.state.matches.find((d) => d.id === match.id),
|
|
2094
|
-
}
|
|
2176
|
+
const { loaderPromise, loadPromise } =
|
|
2177
|
+
this.getMatch(matchId)!
|
|
2095
2178
|
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
await fetchAndResolveInLoaderLifetime()
|
|
2099
|
-
} catch (err) {
|
|
2100
|
-
checkLatest()
|
|
2101
|
-
handleRedirectAndNotFound(match, err)
|
|
2179
|
+
loaderPromise?.resolve()
|
|
2180
|
+
loadPromise?.resolve()
|
|
2102
2181
|
}
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
// If the route is successful and still fresh, just resolve
|
|
2106
|
-
if (
|
|
2107
|
-
match.status === 'success' &&
|
|
2108
|
-
(match.invalid || (shouldReload ?? age > staleAge))
|
|
2109
|
-
) {
|
|
2110
|
-
;(async () => {
|
|
2111
|
-
try {
|
|
2112
|
-
await fetchWithRedirectAndNotFound()
|
|
2113
|
-
} catch (err) {}
|
|
2114
|
-
})()
|
|
2115
|
-
return
|
|
2116
|
-
}
|
|
2117
2182
|
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
matchPromises.push(createValidateResolvedMatchPromise())
|
|
2183
|
+
updateMatch(matchId, (prev) => ({
|
|
2184
|
+
...prev,
|
|
2185
|
+
isFetching: false,
|
|
2186
|
+
loaderPromise: undefined,
|
|
2187
|
+
}))
|
|
2188
|
+
})(),
|
|
2189
|
+
)
|
|
2126
2190
|
})
|
|
2127
2191
|
|
|
2128
2192
|
await Promise.all(matchPromises)
|
|
2129
2193
|
|
|
2130
|
-
checkLatest()
|
|
2131
|
-
|
|
2132
2194
|
resolveAll()
|
|
2133
2195
|
} catch (err) {
|
|
2134
2196
|
rejectAll(err)
|
|
@@ -2152,7 +2214,9 @@ export class Router<
|
|
|
2152
2214
|
const invalidate = (d: MakeRouteMatch<TRouteTree>) => ({
|
|
2153
2215
|
...d,
|
|
2154
2216
|
invalid: true,
|
|
2155
|
-
...(d.status === 'error'
|
|
2217
|
+
...(d.status === 'error'
|
|
2218
|
+
? ({ status: 'pending', error: undefined } as const)
|
|
2219
|
+
: {}),
|
|
2156
2220
|
})
|
|
2157
2221
|
|
|
2158
2222
|
this.__store.setState((s) => ({
|
|
@@ -2242,29 +2306,24 @@ export class Router<
|
|
|
2242
2306
|
})
|
|
2243
2307
|
})
|
|
2244
2308
|
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
const leafMatch = last(matches)
|
|
2251
|
-
const currentLeafMatch = last(this.state.matches)
|
|
2252
|
-
const pendingLeafMatch = last(this.state.pendingMatches ?? [])
|
|
2253
|
-
|
|
2254
|
-
if (
|
|
2255
|
-
leafMatch &&
|
|
2256
|
-
(currentLeafMatch?.id === leafMatch.id ||
|
|
2257
|
-
pendingLeafMatch?.id === leafMatch.id)
|
|
2258
|
-
) {
|
|
2259
|
-
return undefined
|
|
2260
|
-
}
|
|
2309
|
+
const activeMatchIds = new Set(
|
|
2310
|
+
[...this.state.matches, ...(this.state.pendingMatches ?? [])].map(
|
|
2311
|
+
(d) => d.id,
|
|
2312
|
+
),
|
|
2313
|
+
)
|
|
2261
2314
|
|
|
2262
2315
|
try {
|
|
2263
2316
|
matches = await this.loadMatches({
|
|
2264
2317
|
matches,
|
|
2265
2318
|
location: next,
|
|
2266
2319
|
preload: true,
|
|
2267
|
-
|
|
2320
|
+
updateMatch: (id, updater) => {
|
|
2321
|
+
if (activeMatchIds.has(id)) {
|
|
2322
|
+
matches = matches.map((d) => (d.id === id ? updater(d) : d))
|
|
2323
|
+
} else {
|
|
2324
|
+
this.updateMatch(id, updater)
|
|
2325
|
+
}
|
|
2326
|
+
},
|
|
2268
2327
|
})
|
|
2269
2328
|
|
|
2270
2329
|
return matches
|
|
@@ -2363,7 +2422,7 @@ export class Router<
|
|
|
2363
2422
|
}
|
|
2364
2423
|
}
|
|
2365
2424
|
|
|
2366
|
-
hydrate =
|
|
2425
|
+
hydrate = () => {
|
|
2367
2426
|
// Client hydrates from window
|
|
2368
2427
|
let ctx: HydrationCtx | undefined
|
|
2369
2428
|
|
|
@@ -2457,7 +2516,18 @@ export class Router<
|
|
|
2457
2516
|
)
|
|
2458
2517
|
}
|
|
2459
2518
|
|
|
2460
|
-
|
|
2519
|
+
_handleNotFound = (
|
|
2520
|
+
matches: Array<AnyRouteMatch>,
|
|
2521
|
+
err: NotFoundError,
|
|
2522
|
+
{
|
|
2523
|
+
updateMatch = this.updateMatch,
|
|
2524
|
+
}: {
|
|
2525
|
+
updateMatch?: (
|
|
2526
|
+
id: string,
|
|
2527
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
2528
|
+
) => void
|
|
2529
|
+
} = {},
|
|
2530
|
+
) => {
|
|
2461
2531
|
const matchesByRouteId = Object.fromEntries(
|
|
2462
2532
|
matches.map((match) => [match.routeId, match]),
|
|
2463
2533
|
) as Record<string, AnyRouteMatch>
|
|
@@ -2488,11 +2558,20 @@ export class Router<
|
|
|
2488
2558
|
invariant(match, 'Could not find match for route: ' + routeCursor.id)
|
|
2489
2559
|
|
|
2490
2560
|
// Assign the error to the match
|
|
2491
|
-
|
|
2561
|
+
|
|
2562
|
+
updateMatch(match.id, (prev) => ({
|
|
2563
|
+
...prev,
|
|
2492
2564
|
status: 'notFound',
|
|
2493
2565
|
error: err,
|
|
2494
2566
|
isFetching: false,
|
|
2495
|
-
}
|
|
2567
|
+
}))
|
|
2568
|
+
|
|
2569
|
+
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
2570
|
+
err.routeId = routeCursor.parentRoute.id
|
|
2571
|
+
this._handleNotFound(matches, err, {
|
|
2572
|
+
updateMatch,
|
|
2573
|
+
})
|
|
2574
|
+
}
|
|
2496
2575
|
}
|
|
2497
2576
|
|
|
2498
2577
|
hasNotFoundMatch = () => {
|
|
@@ -2500,12 +2579,6 @@ export class Router<
|
|
|
2500
2579
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
2501
2580
|
)
|
|
2502
2581
|
}
|
|
2503
|
-
|
|
2504
|
-
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
2505
|
-
// state.matches
|
|
2506
|
-
// .find((d) => d.id === matchId)
|
|
2507
|
-
// ?.__promisesByKey[key]?.resolve(value)
|
|
2508
|
-
// }
|
|
2509
2582
|
}
|
|
2510
2583
|
|
|
2511
2584
|
// A function that takes an import() argument which is a function and returns a new function that will
|
|
@@ -2531,6 +2604,7 @@ export function getInitialRouterState(
|
|
|
2531
2604
|
location: ParsedLocation,
|
|
2532
2605
|
): RouterState<any> {
|
|
2533
2606
|
return {
|
|
2607
|
+
loadedAt: 0,
|
|
2534
2608
|
isLoading: false,
|
|
2535
2609
|
isTransitioning: false,
|
|
2536
2610
|
status: 'idle',
|