@tanstack/react-router 1.45.2 → 1.45.4
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 +4 -1
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +5 -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 +464 -407
- 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 +5 -2
- package/dist/esm/Matches.js +4 -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 +466 -409
- 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 +10 -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 +647 -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,12 @@ export class Router<
|
|
|
1134
1126
|
}
|
|
1135
1127
|
|
|
1136
1128
|
cancelMatch = (id: string) => {
|
|
1137
|
-
|
|
1129
|
+
const match = this.getMatch(id)
|
|
1130
|
+
|
|
1131
|
+
if (!match) return
|
|
1132
|
+
|
|
1133
|
+
match.abortController.abort()
|
|
1134
|
+
clearTimeout(match.pendingTimeout)
|
|
1138
1135
|
}
|
|
1139
1136
|
|
|
1140
1137
|
cancelMatches = () => {
|
|
@@ -1367,12 +1364,13 @@ export class Router<
|
|
|
1367
1364
|
return buildWithMatches(opts)
|
|
1368
1365
|
}
|
|
1369
1366
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1367
|
+
commitLocationPromise: undefined | ControlledPromise<void>
|
|
1368
|
+
|
|
1369
|
+
commitLocation = ({
|
|
1372
1370
|
viewTransition,
|
|
1373
1371
|
ignoreBlocker,
|
|
1374
1372
|
...next
|
|
1375
|
-
}: ParsedLocation & CommitLocationOptions) => {
|
|
1373
|
+
}: ParsedLocation & CommitLocationOptions): Promise<void> => {
|
|
1376
1374
|
const isSameState = () => {
|
|
1377
1375
|
// `state.key` is ignored but may still be provided when navigating,
|
|
1378
1376
|
// temporarily add the previous key to the next state so it doesn't affect
|
|
@@ -1386,6 +1384,11 @@ export class Router<
|
|
|
1386
1384
|
|
|
1387
1385
|
const isSameUrl = this.latestLocation.href === next.href
|
|
1388
1386
|
|
|
1387
|
+
const previousCommitPromise = this.commitLocationPromise
|
|
1388
|
+
this.commitLocationPromise = createControlledPromise<void>(() => {
|
|
1389
|
+
previousCommitPromise?.resolve()
|
|
1390
|
+
})
|
|
1391
|
+
|
|
1389
1392
|
// Don't commit to history if nothing changed
|
|
1390
1393
|
if (isSameUrl && isSameState()) {
|
|
1391
1394
|
this.load()
|
|
@@ -1432,13 +1435,16 @@ export class Router<
|
|
|
1432
1435
|
|
|
1433
1436
|
this.resetNextScroll = next.resetScroll ?? true
|
|
1434
1437
|
|
|
1435
|
-
|
|
1438
|
+
if (!this.history.subscribers.size) {
|
|
1439
|
+
this.load()
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return this.commitLocationPromise
|
|
1436
1443
|
}
|
|
1437
1444
|
|
|
1438
1445
|
buildAndCommitLocation = ({
|
|
1439
1446
|
replace,
|
|
1440
1447
|
resetScroll,
|
|
1441
|
-
startTransition,
|
|
1442
1448
|
viewTransition,
|
|
1443
1449
|
ignoreBlocker,
|
|
1444
1450
|
...rest
|
|
@@ -1446,7 +1452,6 @@ export class Router<
|
|
|
1446
1452
|
const location = this.buildLocation(rest as any)
|
|
1447
1453
|
return this.commitLocation({
|
|
1448
1454
|
...location,
|
|
1449
|
-
startTransition,
|
|
1450
1455
|
viewTransition,
|
|
1451
1456
|
replace,
|
|
1452
1457
|
resetScroll,
|
|
@@ -1471,7 +1476,7 @@ export class Router<
|
|
|
1471
1476
|
|
|
1472
1477
|
invariant(
|
|
1473
1478
|
!isExternal,
|
|
1474
|
-
'Attempting to navigate to external url with
|
|
1479
|
+
'Attempting to navigate to external url with router.navigate!',
|
|
1475
1480
|
)
|
|
1476
1481
|
|
|
1477
1482
|
return this.buildAndCommitLocation({
|
|
@@ -1482,156 +1487,174 @@ export class Router<
|
|
|
1482
1487
|
})
|
|
1483
1488
|
}
|
|
1484
1489
|
|
|
1490
|
+
latestLoadPromise: undefined | Promise<void>
|
|
1491
|
+
|
|
1485
1492
|
load = async (): Promise<void> => {
|
|
1486
1493
|
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1487
1494
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1495
|
+
this.__store.setState((s) => ({
|
|
1496
|
+
...s,
|
|
1497
|
+
loadedAt: Date.now(),
|
|
1498
|
+
}))
|
|
1491
1499
|
|
|
1492
|
-
const promise = createControlledPromise<void>()
|
|
1493
|
-
this.latestLoadPromise = promise
|
|
1494
1500
|
let redirect: ResolvedRedirect | undefined
|
|
1495
1501
|
let notFound: NotFoundError | undefined
|
|
1496
1502
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1503
|
+
const loadPromise = new Promise<void>((resolve) => {
|
|
1504
|
+
this.startReactTransition(async () => {
|
|
1505
|
+
try {
|
|
1506
|
+
const next = this.latestLocation
|
|
1507
|
+
const prevLocation = this.state.resolvedLocation
|
|
1508
|
+
const pathDidChange = prevLocation.href !== next.href
|
|
1509
|
+
|
|
1510
|
+
// Cancel any pending matches
|
|
1511
|
+
this.cancelMatches()
|
|
1512
|
+
|
|
1513
|
+
let pendingMatches!: Array<AnyRouteMatch>
|
|
1514
|
+
|
|
1515
|
+
this.__store.batch(() => {
|
|
1516
|
+
// this call breaks a route context of destination route after a redirect
|
|
1517
|
+
// we should be fine not eagerly calling this since we call it later
|
|
1518
|
+
// this.cleanCache()
|
|
1519
|
+
|
|
1520
|
+
// Match the routes
|
|
1521
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search)
|
|
1522
|
+
|
|
1523
|
+
// Ingest the new matches
|
|
1524
|
+
this.__store.setState((s) => ({
|
|
1525
|
+
...s,
|
|
1526
|
+
status: 'pending',
|
|
1527
|
+
isLoading: true,
|
|
1528
|
+
location: next,
|
|
1529
|
+
pendingMatches,
|
|
1530
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1531
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1532
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1533
|
+
}),
|
|
1534
|
+
}))
|
|
1535
|
+
})
|
|
1515
1536
|
|
|
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
|
-
})
|
|
1537
|
+
if (!this.state.redirect) {
|
|
1538
|
+
this.emit({
|
|
1539
|
+
type: 'onBeforeNavigate',
|
|
1540
|
+
fromLocation: prevLocation,
|
|
1541
|
+
toLocation: next,
|
|
1542
|
+
pathChanged: pathDidChange,
|
|
1543
|
+
})
|
|
1544
|
+
}
|
|
1529
1545
|
|
|
1530
|
-
if (!this.state.redirect) {
|
|
1531
1546
|
this.emit({
|
|
1532
|
-
type: '
|
|
1547
|
+
type: 'onBeforeLoad',
|
|
1533
1548
|
fromLocation: prevLocation,
|
|
1534
1549
|
toLocation: next,
|
|
1535
1550
|
pathChanged: pathDidChange,
|
|
1536
1551
|
})
|
|
1537
|
-
}
|
|
1538
1552
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1583
|
-
],
|
|
1584
|
-
}
|
|
1553
|
+
await this.loadMatches({
|
|
1554
|
+
matches: pendingMatches,
|
|
1555
|
+
location: next,
|
|
1556
|
+
// eslint-disable-next-line ts/require-await
|
|
1557
|
+
onReady: async () => {
|
|
1558
|
+
// eslint-disable-next-line ts/require-await
|
|
1559
|
+
this.startViewTransition(async () => {
|
|
1560
|
+
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1561
|
+
|
|
1562
|
+
// Commit the pending matches. If a previous match was
|
|
1563
|
+
// removed, place it in the cachedMatches
|
|
1564
|
+
let exitingMatches!: Array<AnyRouteMatch>
|
|
1565
|
+
let enteringMatches!: Array<AnyRouteMatch>
|
|
1566
|
+
let stayingMatches!: Array<AnyRouteMatch>
|
|
1567
|
+
|
|
1568
|
+
this.__store.batch(() => {
|
|
1569
|
+
this.__store.setState((s) => {
|
|
1570
|
+
const previousMatches = s.matches
|
|
1571
|
+
const newMatches = s.pendingMatches || s.matches
|
|
1572
|
+
|
|
1573
|
+
exitingMatches = previousMatches.filter(
|
|
1574
|
+
(match) => !newMatches.find((d) => d.id === match.id),
|
|
1575
|
+
)
|
|
1576
|
+
enteringMatches = newMatches.filter(
|
|
1577
|
+
(match) =>
|
|
1578
|
+
!previousMatches.find((d) => d.id === match.id),
|
|
1579
|
+
)
|
|
1580
|
+
stayingMatches = previousMatches.filter((match) =>
|
|
1581
|
+
newMatches.find((d) => d.id === match.id),
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
return {
|
|
1585
|
+
...s,
|
|
1586
|
+
isLoading: false,
|
|
1587
|
+
matches: newMatches,
|
|
1588
|
+
pendingMatches: undefined,
|
|
1589
|
+
cachedMatches: [
|
|
1590
|
+
...s.cachedMatches,
|
|
1591
|
+
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1592
|
+
],
|
|
1593
|
+
}
|
|
1594
|
+
})
|
|
1595
|
+
this.cleanCache()
|
|
1585
1596
|
})
|
|
1586
|
-
this.cleanCache()
|
|
1587
|
-
})
|
|
1588
1597
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1598
|
+
//
|
|
1599
|
+
;(
|
|
1600
|
+
[
|
|
1601
|
+
[exitingMatches, 'onLeave'],
|
|
1602
|
+
[enteringMatches, 'onEnter'],
|
|
1603
|
+
[stayingMatches, 'onStay'],
|
|
1604
|
+
] as const
|
|
1605
|
+
).forEach(([matches, hook]) => {
|
|
1606
|
+
matches.forEach((match) => {
|
|
1607
|
+
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1608
|
+
})
|
|
1599
1609
|
})
|
|
1600
1610
|
})
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
})
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1611
|
+
},
|
|
1612
|
+
})
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
if (isResolvedRedirect(err)) {
|
|
1615
|
+
redirect = err
|
|
1616
|
+
if (!this.isServer) {
|
|
1617
|
+
this.navigate({ ...err, replace: true, __isRedirect: true })
|
|
1618
|
+
}
|
|
1619
|
+
} else if (isNotFound(err)) {
|
|
1620
|
+
notFound = err
|
|
1610
1621
|
}
|
|
1611
|
-
} else if (isNotFound(err)) {
|
|
1612
|
-
notFound = err
|
|
1613
|
-
}
|
|
1614
1622
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1623
|
+
this.__store.setState((s) => ({
|
|
1624
|
+
...s,
|
|
1625
|
+
statusCode: redirect
|
|
1626
|
+
? redirect.statusCode
|
|
1627
|
+
: notFound
|
|
1628
|
+
? 404
|
|
1629
|
+
: s.matches.some((d) => d.status === 'error')
|
|
1630
|
+
? 500
|
|
1631
|
+
: 200,
|
|
1632
|
+
redirect,
|
|
1633
|
+
}))
|
|
1634
|
+
}
|
|
1627
1635
|
|
|
1628
|
-
|
|
1636
|
+
if (this.latestLoadPromise === loadPromise) {
|
|
1637
|
+
this.commitLocationPromise?.resolve()
|
|
1638
|
+
this.latestLoadPromise = undefined
|
|
1639
|
+
this.commitLocationPromise = undefined
|
|
1640
|
+
}
|
|
1641
|
+
resolve()
|
|
1642
|
+
})
|
|
1629
1643
|
})
|
|
1630
1644
|
|
|
1631
|
-
|
|
1645
|
+
this.latestLoadPromise = loadPromise
|
|
1646
|
+
|
|
1647
|
+
await loadPromise
|
|
1648
|
+
|
|
1649
|
+
while (
|
|
1650
|
+
(this.latestLoadPromise as any) &&
|
|
1651
|
+
loadPromise !== this.latestLoadPromise
|
|
1652
|
+
) {
|
|
1653
|
+
await this.latestLoadPromise
|
|
1654
|
+
}
|
|
1632
1655
|
}
|
|
1633
1656
|
|
|
1634
|
-
startViewTransition =
|
|
1657
|
+
startViewTransition = (fn: () => Promise<void>) => {
|
|
1635
1658
|
// Determine if we should start a view transition from the navigation
|
|
1636
1659
|
// or from the router default
|
|
1637
1660
|
const shouldViewTransition =
|
|
@@ -1648,18 +1671,54 @@ export class Router<
|
|
|
1648
1671
|
?.startViewTransition?.(fn) || fn()
|
|
1649
1672
|
}
|
|
1650
1673
|
|
|
1674
|
+
updateMatch = (
|
|
1675
|
+
id: string,
|
|
1676
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
1677
|
+
) => {
|
|
1678
|
+
let updated!: AnyRouteMatch
|
|
1679
|
+
const isPending = this.state.pendingMatches?.find((d) => d.id === id)
|
|
1680
|
+
const isMatched = this.state.matches.find((d) => d.id === id)
|
|
1681
|
+
|
|
1682
|
+
const matchesKey = isPending
|
|
1683
|
+
? 'pendingMatches'
|
|
1684
|
+
: isMatched
|
|
1685
|
+
? 'matches'
|
|
1686
|
+
: 'cachedMatches'
|
|
1687
|
+
|
|
1688
|
+
this.__store.setState((s) => ({
|
|
1689
|
+
...s,
|
|
1690
|
+
[matchesKey]: s[matchesKey]?.map((d) =>
|
|
1691
|
+
d.id === id ? (updated = updater(d)) : d,
|
|
1692
|
+
),
|
|
1693
|
+
}))
|
|
1694
|
+
|
|
1695
|
+
return updated
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
getMatch = (matchId: string) => {
|
|
1699
|
+
return [
|
|
1700
|
+
...this.state.cachedMatches,
|
|
1701
|
+
...(this.state.pendingMatches ?? []),
|
|
1702
|
+
...this.state.matches,
|
|
1703
|
+
].find((d) => d.id === matchId)
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1651
1706
|
loadMatches = async ({
|
|
1652
|
-
checkLatest,
|
|
1653
1707
|
location,
|
|
1654
1708
|
matches,
|
|
1655
1709
|
preload,
|
|
1656
1710
|
onReady,
|
|
1711
|
+
updateMatch = this.updateMatch,
|
|
1657
1712
|
}: {
|
|
1658
|
-
checkLatest: () => void
|
|
1659
1713
|
location: ParsedLocation
|
|
1660
1714
|
matches: Array<AnyRouteMatch>
|
|
1661
1715
|
preload?: boolean
|
|
1662
1716
|
onReady?: () => Promise<void>
|
|
1717
|
+
updateMatch?: (
|
|
1718
|
+
id: string,
|
|
1719
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
1720
|
+
) => void
|
|
1721
|
+
getMatch?: (matchId: string) => AnyRouteMatch | undefined
|
|
1663
1722
|
}): Promise<Array<MakeRouteMatch>> => {
|
|
1664
1723
|
let firstBadMatchIndex: number | undefined
|
|
1665
1724
|
let rendered = false
|
|
@@ -1675,38 +1734,10 @@ export class Router<
|
|
|
1675
1734
|
triggerOnReady()
|
|
1676
1735
|
}
|
|
1677
1736
|
|
|
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
1737
|
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
1706
1738
|
if (isResolvedRedirect(err)) throw err
|
|
1707
1739
|
|
|
1708
1740
|
if (isRedirect(err) || isNotFound(err)) {
|
|
1709
|
-
// if (!rendered) {
|
|
1710
1741
|
updateMatch(match.id, (prev) => ({
|
|
1711
1742
|
...prev,
|
|
1712
1743
|
status: isRedirect(err)
|
|
@@ -1716,19 +1747,26 @@ export class Router<
|
|
|
1716
1747
|
: 'error',
|
|
1717
1748
|
isFetching: false,
|
|
1718
1749
|
error: err,
|
|
1750
|
+
beforeLoadPromise: undefined,
|
|
1751
|
+
loaderPromise: undefined,
|
|
1719
1752
|
}))
|
|
1720
|
-
// }
|
|
1721
1753
|
|
|
1722
1754
|
if (!(err as any).routeId) {
|
|
1723
1755
|
;(err as any).routeId = match.routeId
|
|
1724
1756
|
}
|
|
1725
1757
|
|
|
1758
|
+
match.beforeLoadPromise?.resolve()
|
|
1759
|
+
match.loaderPromise?.resolve()
|
|
1760
|
+
match.loadPromise?.resolve()
|
|
1761
|
+
|
|
1726
1762
|
if (isRedirect(err)) {
|
|
1727
1763
|
rendered = true
|
|
1728
1764
|
err = this.resolveRedirect({ ...err, _fromLocation: location })
|
|
1729
1765
|
throw err
|
|
1730
1766
|
} else if (isNotFound(err)) {
|
|
1731
|
-
this.
|
|
1767
|
+
this._handleNotFound(matches, err, {
|
|
1768
|
+
updateMatch,
|
|
1769
|
+
})
|
|
1732
1770
|
throw err
|
|
1733
1771
|
}
|
|
1734
1772
|
}
|
|
@@ -1738,397 +1776,429 @@ export class Router<
|
|
|
1738
1776
|
await new Promise<void>((resolveAll, rejectAll) => {
|
|
1739
1777
|
;(async () => {
|
|
1740
1778
|
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)
|
|
1779
|
+
const handleSerialError = (
|
|
1780
|
+
index: number,
|
|
1781
|
+
err: any,
|
|
1782
|
+
routerCode: string,
|
|
1783
|
+
) => {
|
|
1784
|
+
const { id: matchId, routeId } = matches[index]!
|
|
1785
|
+
const route = this.looseRoutesById[routeId]!
|
|
1786
|
+
|
|
1787
|
+
// Much like suspense, we use a promise here to know if
|
|
1788
|
+
// we've been outdated by a new loadMatches call and
|
|
1789
|
+
// should abort the current async operation
|
|
1790
|
+
if (err instanceof Promise) {
|
|
1791
|
+
throw err
|
|
1774
1792
|
}
|
|
1775
1793
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1794
|
+
err.routerCode = routerCode
|
|
1795
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
1796
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
1779
1797
|
|
|
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
|
-
}
|
|
1798
|
+
try {
|
|
1799
|
+
route.options.onError?.(err)
|
|
1800
|
+
} catch (errorHandlerErr) {
|
|
1801
|
+
err = errorHandlerErr
|
|
1802
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
1803
|
+
}
|
|
1800
1804
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
handleRedirectAndNotFound(match, err)
|
|
1805
|
+
updateMatch(matchId, (prev) => {
|
|
1806
|
+
prev.beforeLoadPromise?.resolve()
|
|
1804
1807
|
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
} catch (errorHandlerErr) {
|
|
1808
|
-
err = errorHandlerErr
|
|
1809
|
-
handleRedirectAndNotFound(match, err)
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
matches[index] = match = updateMatch(match.id, () => ({
|
|
1813
|
-
...match,
|
|
1808
|
+
return {
|
|
1809
|
+
...prev,
|
|
1814
1810
|
error: err,
|
|
1815
1811
|
status: 'error',
|
|
1812
|
+
isFetching: false,
|
|
1816
1813
|
updatedAt: Date.now(),
|
|
1817
1814
|
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,
|
|
1815
|
+
beforeLoadPromise: undefined,
|
|
1842
1816
|
}
|
|
1817
|
+
})
|
|
1818
|
+
}
|
|
1843
1819
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1820
|
+
for (const [index, { id: matchId, routeId }] of matches.entries()) {
|
|
1821
|
+
const existingMatch = this.getMatch(matchId)!
|
|
1822
|
+
|
|
1823
|
+
if (
|
|
1824
|
+
// If we are in the middle of a load, either of these will be present
|
|
1825
|
+
// (not to be confused with `loadPromise`, which is always defined)
|
|
1826
|
+
existingMatch.beforeLoadPromise ||
|
|
1827
|
+
existingMatch.loaderPromise
|
|
1828
|
+
) {
|
|
1829
|
+
// Wait for the beforeLoad to resolve before we continue
|
|
1830
|
+
await existingMatch.beforeLoadPromise
|
|
1831
|
+
} else {
|
|
1832
|
+
// If we are not in the middle of a load, start it
|
|
1833
|
+
try {
|
|
1834
|
+
updateMatch(matchId, (prev) => ({
|
|
1835
|
+
...prev,
|
|
1836
|
+
loadPromise: createControlledPromise<void>(() => {
|
|
1837
|
+
prev.loadPromise?.resolve()
|
|
1838
|
+
}),
|
|
1839
|
+
beforeLoadPromise: createControlledPromise<void>(),
|
|
1840
|
+
}))
|
|
1841
|
+
|
|
1842
|
+
const route = this.looseRoutesById[routeId]!
|
|
1843
|
+
const abortController = new AbortController()
|
|
1844
|
+
|
|
1845
|
+
const parentMatchId = matches[index - 1]?.id
|
|
1846
|
+
|
|
1847
|
+
const getParentContext = () => {
|
|
1848
|
+
if (!parentMatchId) {
|
|
1849
|
+
return (this.options.context as any) ?? {}
|
|
1850
|
+
}
|
|
1856
1851
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1852
|
+
return (
|
|
1853
|
+
this.getMatch(parentMatchId)!.context ??
|
|
1854
|
+
this.options.context ??
|
|
1855
|
+
{}
|
|
1856
|
+
)
|
|
1857
|
+
}
|
|
1861
1858
|
|
|
1862
|
-
|
|
1859
|
+
const pendingMs =
|
|
1860
|
+
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1861
|
+
|
|
1862
|
+
const shouldPending = !!(
|
|
1863
|
+
onReady &&
|
|
1864
|
+
!this.isServer &&
|
|
1865
|
+
!preload &&
|
|
1866
|
+
(route.options.loader || route.options.beforeLoad) &&
|
|
1867
|
+
typeof pendingMs === 'number' &&
|
|
1868
|
+
pendingMs !== Infinity &&
|
|
1869
|
+
(route.options.pendingComponent ??
|
|
1870
|
+
this.options.defaultPendingComponent)
|
|
1871
|
+
)
|
|
1863
1872
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1873
|
+
let pendingTimeout: ReturnType<typeof setTimeout>
|
|
1874
|
+
|
|
1875
|
+
if (shouldPending) {
|
|
1876
|
+
// If we might show a pending component, we need to wait for the
|
|
1877
|
+
// pending promise to resolve before we start showing that state
|
|
1878
|
+
pendingTimeout = setTimeout(() => {
|
|
1879
|
+
try {
|
|
1880
|
+
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1881
|
+
// the pending component can start rendering
|
|
1882
|
+
triggerOnReady()
|
|
1883
|
+
} catch {}
|
|
1884
|
+
}, pendingMs)
|
|
1885
|
+
}
|
|
1870
1886
|
|
|
1871
|
-
|
|
1872
|
-
...parentContext,
|
|
1873
|
-
...beforeLoadContext,
|
|
1874
|
-
}
|
|
1887
|
+
const { paramsError, searchError } = this.getMatch(matchId)!
|
|
1875
1888
|
|
|
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
|
-
}
|
|
1889
|
+
if (paramsError) {
|
|
1890
|
+
handleSerialError(index, paramsError, 'PARSE_PARAMS')
|
|
1891
|
+
}
|
|
1891
1892
|
|
|
1892
|
-
|
|
1893
|
+
if (searchError) {
|
|
1894
|
+
handleSerialError(index, searchError, 'VALIDATE_SEARCH')
|
|
1895
|
+
}
|
|
1893
1896
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1897
|
+
const parentContext = getParentContext()
|
|
1898
|
+
|
|
1899
|
+
updateMatch(matchId, (prev) => ({
|
|
1900
|
+
...prev,
|
|
1901
|
+
isFetching: 'beforeLoad',
|
|
1902
|
+
fetchCount: prev.fetchCount + 1,
|
|
1903
|
+
routeContext: replaceEqualDeep(
|
|
1904
|
+
prev.routeContext,
|
|
1905
|
+
parentContext,
|
|
1906
|
+
),
|
|
1907
|
+
context: replaceEqualDeep(prev.context, parentContext),
|
|
1908
|
+
abortController,
|
|
1909
|
+
pendingTimeout,
|
|
1910
|
+
}))
|
|
1911
|
+
|
|
1912
|
+
const { search, params, routeContext, cause } =
|
|
1913
|
+
this.getMatch(matchId)!
|
|
1914
|
+
|
|
1915
|
+
const beforeLoadFnContext = {
|
|
1916
|
+
search,
|
|
1917
|
+
abortController,
|
|
1918
|
+
params,
|
|
1919
|
+
preload: !!preload,
|
|
1920
|
+
context: routeContext,
|
|
1921
|
+
location,
|
|
1922
|
+
navigate: (opts: any) =>
|
|
1923
|
+
this.navigate({ ...opts, _fromLocation: location }),
|
|
1924
|
+
buildLocation: this.buildLocation,
|
|
1925
|
+
cause: preload ? 'preload' : cause,
|
|
1926
|
+
}
|
|
1896
1927
|
|
|
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
|
-
}
|
|
1928
|
+
const beforeLoadContext =
|
|
1929
|
+
(await route.options.beforeLoad?.(beforeLoadFnContext)) ??
|
|
1930
|
+
{}
|
|
1915
1931
|
|
|
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
|
-
}
|
|
1932
|
+
if (
|
|
1933
|
+
isRedirect(beforeLoadContext) ||
|
|
1934
|
+
isNotFound(beforeLoadContext)
|
|
1935
|
+
) {
|
|
1936
|
+
handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD')
|
|
1940
1937
|
}
|
|
1941
1938
|
|
|
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
|
-
)
|
|
1939
|
+
updateMatch(matchId, (prev) => {
|
|
1940
|
+
const routeContext = {
|
|
1941
|
+
...prev.routeContext,
|
|
1942
|
+
...beforeLoadContext,
|
|
1998
1943
|
}
|
|
1999
1944
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1945
|
+
return {
|
|
1946
|
+
...prev,
|
|
1947
|
+
routeContext: replaceEqualDeep(
|
|
1948
|
+
prev.routeContext,
|
|
1949
|
+
routeContext,
|
|
1950
|
+
),
|
|
1951
|
+
context: replaceEqualDeep(prev.context, routeContext),
|
|
1952
|
+
abortController,
|
|
2006
1953
|
}
|
|
2007
|
-
|
|
1954
|
+
})
|
|
1955
|
+
} catch (err) {
|
|
1956
|
+
handleSerialError(index, err, 'BEFORE_LOAD')
|
|
1957
|
+
}
|
|
2008
1958
|
|
|
2009
|
-
|
|
1959
|
+
updateMatch(matchId, (prev) => {
|
|
1960
|
+
prev.beforeLoadPromise?.resolve()
|
|
2010
1961
|
|
|
2011
|
-
|
|
2012
|
-
|
|
1962
|
+
return {
|
|
1963
|
+
...prev,
|
|
1964
|
+
beforeLoadPromise: undefined,
|
|
1965
|
+
isFetching: false,
|
|
1966
|
+
}
|
|
1967
|
+
})
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
2013
1970
|
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
match,
|
|
2017
|
-
params: match.params,
|
|
2018
|
-
loaderData,
|
|
2019
|
-
})
|
|
1971
|
+
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
1972
|
+
const matchPromises: Array<Promise<any>> = []
|
|
2020
1973
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
1974
|
+
validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
|
|
1975
|
+
matchPromises.push(
|
|
1976
|
+
(async () => {
|
|
1977
|
+
const { loaderPromise: prevLoaderPromise } =
|
|
1978
|
+
this.getMatch(matchId)!
|
|
1979
|
+
|
|
1980
|
+
if (prevLoaderPromise) {
|
|
1981
|
+
await prevLoaderPromise
|
|
1982
|
+
} else {
|
|
1983
|
+
const parentMatchPromise = matchPromises[index - 1]
|
|
1984
|
+
const route = this.looseRoutesById[routeId]!
|
|
1985
|
+
|
|
1986
|
+
const getLoaderContext = (): LoaderFnContext => {
|
|
1987
|
+
const {
|
|
1988
|
+
params,
|
|
1989
|
+
loaderDeps,
|
|
1990
|
+
abortController,
|
|
1991
|
+
context,
|
|
1992
|
+
cause,
|
|
1993
|
+
} = this.getMatch(matchId)!
|
|
1994
|
+
|
|
1995
|
+
return {
|
|
1996
|
+
params,
|
|
1997
|
+
deps: loaderDeps,
|
|
1998
|
+
preload: !!preload,
|
|
1999
|
+
parentMatchPromise,
|
|
2000
|
+
abortController: abortController,
|
|
2001
|
+
context,
|
|
2002
|
+
location,
|
|
2003
|
+
navigate: (opts) =>
|
|
2004
|
+
this.navigate({ ...opts, _fromLocation: location }),
|
|
2005
|
+
cause: preload ? 'preload' : cause,
|
|
2006
|
+
route,
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2024
2009
|
|
|
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
|
|
2010
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
2011
|
+
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2038
2012
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2013
|
+
const staleAge = preload
|
|
2014
|
+
? (route.options.preloadStaleTime ??
|
|
2015
|
+
this.options.defaultPreloadStaleTime ??
|
|
2016
|
+
30_000) // 30 seconds for preloads by default
|
|
2017
|
+
: (route.options.staleTime ??
|
|
2018
|
+
this.options.defaultStaleTime ??
|
|
2019
|
+
0)
|
|
2041
2020
|
|
|
2042
|
-
|
|
2021
|
+
const shouldReloadOption = route.options.shouldReload
|
|
2043
2022
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2023
|
+
// Default to reloading the route all the time
|
|
2024
|
+
// Allow shouldReload to get the last say,
|
|
2025
|
+
// if provided.
|
|
2026
|
+
const shouldReload =
|
|
2027
|
+
typeof shouldReloadOption === 'function'
|
|
2028
|
+
? shouldReloadOption(getLoaderContext())
|
|
2029
|
+
: shouldReloadOption
|
|
2050
2030
|
|
|
2051
|
-
|
|
2031
|
+
updateMatch(matchId, (prev) => ({
|
|
2052
2032
|
...prev,
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2033
|
+
loaderPromise: createControlledPromise<void>(),
|
|
2034
|
+
preload:
|
|
2035
|
+
!!preload &&
|
|
2036
|
+
!this.state.matches.find((d) => d.id === matchId),
|
|
2056
2037
|
}))
|
|
2057
|
-
}
|
|
2058
2038
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2039
|
+
const runLoader = async () => {
|
|
2040
|
+
try {
|
|
2041
|
+
// If the Matches component rendered
|
|
2042
|
+
// the pending component and needs to show it for
|
|
2043
|
+
// a minimum duration, we''ll wait for it to resolve
|
|
2044
|
+
// before committing to the match and resolving
|
|
2045
|
+
// the loadPromise
|
|
2046
|
+
const potentialPendingMinPromise = async () => {
|
|
2047
|
+
const latestMatch = this.getMatch(matchId)!
|
|
2048
|
+
|
|
2049
|
+
if (latestMatch.minPendingPromise) {
|
|
2050
|
+
await latestMatch.minPendingPromise
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// Actually run the loader and handle the result
|
|
2055
|
+
try {
|
|
2056
|
+
route._lazyPromise =
|
|
2057
|
+
route._lazyPromise ||
|
|
2058
|
+
(route.lazyFn
|
|
2059
|
+
? route.lazyFn().then((lazyRoute) => {
|
|
2060
|
+
Object.assign(
|
|
2061
|
+
route.options,
|
|
2062
|
+
lazyRoute.options,
|
|
2063
|
+
)
|
|
2064
|
+
})
|
|
2065
|
+
: Promise.resolve())
|
|
2066
|
+
|
|
2067
|
+
// If for some reason lazy resolves more lazy components...
|
|
2068
|
+
// We'll wait for that before pre attempt to preload any
|
|
2069
|
+
// components themselves.
|
|
2070
|
+
const componentsPromise =
|
|
2071
|
+
this.getMatch(matchId)!.componentsPromise ||
|
|
2072
|
+
route._lazyPromise.then(() =>
|
|
2073
|
+
Promise.all(
|
|
2074
|
+
componentTypes.map(async (type) => {
|
|
2075
|
+
const component = route.options[type]
|
|
2076
|
+
|
|
2077
|
+
if ((component as any)?.preload) {
|
|
2078
|
+
await (component as any).preload()
|
|
2079
|
+
}
|
|
2080
|
+
}),
|
|
2081
|
+
),
|
|
2082
|
+
)
|
|
2083
|
+
|
|
2084
|
+
// Otherwise, load the route
|
|
2085
|
+
updateMatch(matchId, (prev) => ({
|
|
2086
|
+
...prev,
|
|
2087
|
+
isFetching: 'loader',
|
|
2088
|
+
componentsPromise,
|
|
2089
|
+
}))
|
|
2090
|
+
|
|
2091
|
+
// Lazy option can modify the route options,
|
|
2092
|
+
// so we need to wait for it to resolve before
|
|
2093
|
+
// we can use the options
|
|
2094
|
+
await route._lazyPromise
|
|
2095
|
+
|
|
2096
|
+
// Kick off the loader!
|
|
2097
|
+
let loaderData =
|
|
2098
|
+
await route.options.loader?.(getLoaderContext())
|
|
2099
|
+
|
|
2100
|
+
if (this.serializeLoaderData) {
|
|
2101
|
+
loaderData = this.serializeLoaderData(loaderData, {
|
|
2102
|
+
router: this,
|
|
2103
|
+
match: this.getMatch(matchId)!,
|
|
2104
|
+
})
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
handleRedirectAndNotFound(
|
|
2108
|
+
this.getMatch(matchId)!,
|
|
2109
|
+
loaderData,
|
|
2110
|
+
)
|
|
2111
|
+
|
|
2112
|
+
await potentialPendingMinPromise()
|
|
2113
|
+
|
|
2114
|
+
const meta = route.options.meta?.({
|
|
2115
|
+
matches,
|
|
2116
|
+
match: this.getMatch(matchId)!,
|
|
2117
|
+
params: this.getMatch(matchId)!.params,
|
|
2118
|
+
loaderData,
|
|
2119
|
+
})
|
|
2120
|
+
|
|
2121
|
+
const headers = route.options.headers?.({
|
|
2122
|
+
loaderData,
|
|
2123
|
+
})
|
|
2124
|
+
|
|
2125
|
+
updateMatch(matchId, (prev) => ({
|
|
2126
|
+
...prev,
|
|
2127
|
+
error: undefined,
|
|
2128
|
+
status: 'success',
|
|
2129
|
+
isFetching: false,
|
|
2130
|
+
updatedAt: Date.now(),
|
|
2131
|
+
loaderData,
|
|
2132
|
+
meta,
|
|
2133
|
+
headers,
|
|
2134
|
+
}))
|
|
2135
|
+
} catch (e) {
|
|
2136
|
+
let error = e
|
|
2137
|
+
|
|
2138
|
+
await potentialPendingMinPromise()
|
|
2139
|
+
|
|
2140
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, e)
|
|
2141
|
+
|
|
2142
|
+
try {
|
|
2143
|
+
route.options.onError?.(e)
|
|
2144
|
+
} catch (onErrorError) {
|
|
2145
|
+
error = onErrorError
|
|
2146
|
+
handleRedirectAndNotFound(
|
|
2147
|
+
this.getMatch(matchId)!,
|
|
2148
|
+
onErrorError,
|
|
2149
|
+
)
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
updateMatch(matchId, (prev) => ({
|
|
2153
|
+
...prev,
|
|
2154
|
+
error,
|
|
2155
|
+
status: 'error',
|
|
2156
|
+
isFetching: false,
|
|
2157
|
+
}))
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// Last but not least, wait for the the component
|
|
2161
|
+
// to be preloaded before we resolve the match
|
|
2162
|
+
await this.getMatch(matchId)!.componentsPromise
|
|
2163
|
+
} catch (err) {
|
|
2164
|
+
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2064
2167
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2168
|
+
// If the route is successful and still fresh, just resolve
|
|
2169
|
+
const { status, invalid } = this.getMatch(matchId)!
|
|
2170
|
+
|
|
2171
|
+
if (
|
|
2172
|
+
status === 'success' &&
|
|
2173
|
+
(invalid || (shouldReload ?? age > staleAge))
|
|
2174
|
+
) {
|
|
2175
|
+
;(async () => {
|
|
2176
|
+
try {
|
|
2177
|
+
await runLoader()
|
|
2178
|
+
} catch (err) {}
|
|
2179
|
+
})()
|
|
2180
|
+
} else if (status !== 'success') {
|
|
2181
|
+
await runLoader()
|
|
2182
|
+
}
|
|
2067
2183
|
|
|
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
|
-
}
|
|
2184
|
+
const { loaderPromise, loadPromise } =
|
|
2185
|
+
this.getMatch(matchId)!
|
|
2095
2186
|
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
await fetchAndResolveInLoaderLifetime()
|
|
2099
|
-
} catch (err) {
|
|
2100
|
-
checkLatest()
|
|
2101
|
-
handleRedirectAndNotFound(match, err)
|
|
2187
|
+
loaderPromise?.resolve()
|
|
2188
|
+
loadPromise?.resolve()
|
|
2102
2189
|
}
|
|
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
2190
|
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
matchPromises.push(createValidateResolvedMatchPromise())
|
|
2191
|
+
updateMatch(matchId, (prev) => ({
|
|
2192
|
+
...prev,
|
|
2193
|
+
isFetching: false,
|
|
2194
|
+
loaderPromise: undefined,
|
|
2195
|
+
}))
|
|
2196
|
+
})(),
|
|
2197
|
+
)
|
|
2126
2198
|
})
|
|
2127
2199
|
|
|
2128
2200
|
await Promise.all(matchPromises)
|
|
2129
2201
|
|
|
2130
|
-
checkLatest()
|
|
2131
|
-
|
|
2132
2202
|
resolveAll()
|
|
2133
2203
|
} catch (err) {
|
|
2134
2204
|
rejectAll(err)
|
|
@@ -2152,7 +2222,9 @@ export class Router<
|
|
|
2152
2222
|
const invalidate = (d: MakeRouteMatch<TRouteTree>) => ({
|
|
2153
2223
|
...d,
|
|
2154
2224
|
invalid: true,
|
|
2155
|
-
...(d.status === 'error'
|
|
2225
|
+
...(d.status === 'error'
|
|
2226
|
+
? ({ status: 'pending', error: undefined } as const)
|
|
2227
|
+
: {}),
|
|
2156
2228
|
})
|
|
2157
2229
|
|
|
2158
2230
|
this.__store.setState((s) => ({
|
|
@@ -2242,29 +2314,24 @@ export class Router<
|
|
|
2242
2314
|
})
|
|
2243
2315
|
})
|
|
2244
2316
|
|
|
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
|
-
}
|
|
2317
|
+
const activeMatchIds = new Set(
|
|
2318
|
+
[...this.state.matches, ...(this.state.pendingMatches ?? [])].map(
|
|
2319
|
+
(d) => d.id,
|
|
2320
|
+
),
|
|
2321
|
+
)
|
|
2261
2322
|
|
|
2262
2323
|
try {
|
|
2263
2324
|
matches = await this.loadMatches({
|
|
2264
2325
|
matches,
|
|
2265
2326
|
location: next,
|
|
2266
2327
|
preload: true,
|
|
2267
|
-
|
|
2328
|
+
updateMatch: (id, updater) => {
|
|
2329
|
+
if (activeMatchIds.has(id)) {
|
|
2330
|
+
matches = matches.map((d) => (d.id === id ? updater(d) : d))
|
|
2331
|
+
} else {
|
|
2332
|
+
this.updateMatch(id, updater)
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2268
2335
|
})
|
|
2269
2336
|
|
|
2270
2337
|
return matches
|
|
@@ -2363,7 +2430,7 @@ export class Router<
|
|
|
2363
2430
|
}
|
|
2364
2431
|
}
|
|
2365
2432
|
|
|
2366
|
-
hydrate =
|
|
2433
|
+
hydrate = () => {
|
|
2367
2434
|
// Client hydrates from window
|
|
2368
2435
|
let ctx: HydrationCtx | undefined
|
|
2369
2436
|
|
|
@@ -2457,7 +2524,18 @@ export class Router<
|
|
|
2457
2524
|
)
|
|
2458
2525
|
}
|
|
2459
2526
|
|
|
2460
|
-
|
|
2527
|
+
_handleNotFound = (
|
|
2528
|
+
matches: Array<AnyRouteMatch>,
|
|
2529
|
+
err: NotFoundError,
|
|
2530
|
+
{
|
|
2531
|
+
updateMatch = this.updateMatch,
|
|
2532
|
+
}: {
|
|
2533
|
+
updateMatch?: (
|
|
2534
|
+
id: string,
|
|
2535
|
+
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
2536
|
+
) => void
|
|
2537
|
+
} = {},
|
|
2538
|
+
) => {
|
|
2461
2539
|
const matchesByRouteId = Object.fromEntries(
|
|
2462
2540
|
matches.map((match) => [match.routeId, match]),
|
|
2463
2541
|
) as Record<string, AnyRouteMatch>
|
|
@@ -2488,11 +2566,20 @@ export class Router<
|
|
|
2488
2566
|
invariant(match, 'Could not find match for route: ' + routeCursor.id)
|
|
2489
2567
|
|
|
2490
2568
|
// Assign the error to the match
|
|
2491
|
-
|
|
2569
|
+
|
|
2570
|
+
updateMatch(match.id, (prev) => ({
|
|
2571
|
+
...prev,
|
|
2492
2572
|
status: 'notFound',
|
|
2493
2573
|
error: err,
|
|
2494
2574
|
isFetching: false,
|
|
2495
|
-
}
|
|
2575
|
+
}))
|
|
2576
|
+
|
|
2577
|
+
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
2578
|
+
err.routeId = routeCursor.parentRoute.id
|
|
2579
|
+
this._handleNotFound(matches, err, {
|
|
2580
|
+
updateMatch,
|
|
2581
|
+
})
|
|
2582
|
+
}
|
|
2496
2583
|
}
|
|
2497
2584
|
|
|
2498
2585
|
hasNotFoundMatch = () => {
|
|
@@ -2500,12 +2587,6 @@ export class Router<
|
|
|
2500
2587
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
2501
2588
|
)
|
|
2502
2589
|
}
|
|
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
2590
|
}
|
|
2510
2591
|
|
|
2511
2592
|
// A function that takes an import() argument which is a function and returns a new function that will
|
|
@@ -2531,6 +2612,7 @@ export function getInitialRouterState(
|
|
|
2531
2612
|
location: ParsedLocation,
|
|
2532
2613
|
): RouterState<any> {
|
|
2533
2614
|
return {
|
|
2615
|
+
loadedAt: 0,
|
|
2534
2616
|
isLoading: false,
|
|
2535
2617
|
isTransitioning: false,
|
|
2536
2618
|
status: 'idle',
|