@tanstack/react-router 1.31.1 → 1.31.2
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/Matches.cjs +1 -5
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/RouterProvider.cjs +2 -2
- package/dist/cjs/RouterProvider.cjs.map +1 -1
- package/dist/cjs/redirects.cjs +4 -0
- package/dist/cjs/redirects.cjs.map +1 -1
- package/dist/cjs/redirects.d.cts +1 -0
- package/dist/cjs/router.cjs +168 -177
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +6 -4
- package/dist/esm/Matches.js +1 -5
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.js +2 -2
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/redirects.d.ts +1 -0
- package/dist/esm/redirects.js +4 -0
- package/dist/esm/redirects.js.map +1 -1
- package/dist/esm/router.d.ts +6 -4
- package/dist/esm/router.js +169 -178
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.tsx +5 -5
- package/src/RouterProvider.tsx +2 -2
- package/src/redirects.ts +4 -0
- package/src/router.ts +240 -269
package/src/router.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
trimPathLeft,
|
|
26
26
|
trimPathRight,
|
|
27
27
|
} from './path'
|
|
28
|
-
import { isRedirect } from './redirects'
|
|
28
|
+
import { isRedirect, isResolvedRedirect } from './redirects'
|
|
29
29
|
import { isNotFound } from './not-found'
|
|
30
30
|
import type * as React from 'react'
|
|
31
31
|
import type {
|
|
@@ -583,10 +583,10 @@ export class Router<
|
|
|
583
583
|
})
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
-
checkLatest = (promise: Promise<void>):
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
586
|
+
checkLatest = (promise: Promise<void>): void => {
|
|
587
|
+
if (this.latestLoadPromise !== promise) {
|
|
588
|
+
throw this.latestLoadPromise
|
|
589
|
+
}
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
parseLocation = (
|
|
@@ -1206,19 +1206,180 @@ export class Router<
|
|
|
1206
1206
|
})
|
|
1207
1207
|
}
|
|
1208
1208
|
|
|
1209
|
+
load = async (): Promise<void> => {
|
|
1210
|
+
const promise = createControlledPromise<void>()
|
|
1211
|
+
this.latestLoadPromise = promise
|
|
1212
|
+
let redirect: ResolvedRedirect | undefined
|
|
1213
|
+
let notFound: NotFoundError | undefined
|
|
1214
|
+
|
|
1215
|
+
this.startReactTransition(async () => {
|
|
1216
|
+
try {
|
|
1217
|
+
const next = this.latestLocation
|
|
1218
|
+
const prevLocation = this.state.resolvedLocation
|
|
1219
|
+
const pathDidChange = prevLocation.href !== next.href
|
|
1220
|
+
|
|
1221
|
+
// Cancel any pending matches
|
|
1222
|
+
this.cancelMatches()
|
|
1223
|
+
|
|
1224
|
+
this.emit({
|
|
1225
|
+
type: 'onBeforeLoad',
|
|
1226
|
+
fromLocation: prevLocation,
|
|
1227
|
+
toLocation: next,
|
|
1228
|
+
pathChanged: pathDidChange,
|
|
1229
|
+
})
|
|
1230
|
+
|
|
1231
|
+
let pendingMatches!: Array<AnyRouteMatch>
|
|
1232
|
+
|
|
1233
|
+
this.__store.batch(() => {
|
|
1234
|
+
this.cleanCache()
|
|
1235
|
+
|
|
1236
|
+
// Match the routes
|
|
1237
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search)
|
|
1238
|
+
|
|
1239
|
+
// Ingest the new matches
|
|
1240
|
+
this.__store.setState((s) => ({
|
|
1241
|
+
...s,
|
|
1242
|
+
status: 'pending',
|
|
1243
|
+
isLoading: true,
|
|
1244
|
+
location: next,
|
|
1245
|
+
pendingMatches,
|
|
1246
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1247
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1248
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1249
|
+
}),
|
|
1250
|
+
}))
|
|
1251
|
+
})
|
|
1252
|
+
|
|
1253
|
+
await this.loadMatches({
|
|
1254
|
+
matches: pendingMatches,
|
|
1255
|
+
location: next,
|
|
1256
|
+
checkLatest: () => this.checkLatest(promise),
|
|
1257
|
+
onReady: async () => {
|
|
1258
|
+
await this.startViewTransition(async () => {
|
|
1259
|
+
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1260
|
+
|
|
1261
|
+
// Commit the pending matches. If a previous match was
|
|
1262
|
+
// removed, place it in the cachedMatches
|
|
1263
|
+
let exitingMatches!: Array<AnyRouteMatch>
|
|
1264
|
+
let enteringMatches!: Array<AnyRouteMatch>
|
|
1265
|
+
let stayingMatches!: Array<AnyRouteMatch>
|
|
1266
|
+
|
|
1267
|
+
this.__store.batch(() => {
|
|
1268
|
+
this.__store.setState((s) => {
|
|
1269
|
+
const previousMatches = s.matches
|
|
1270
|
+
const newMatches = s.pendingMatches || s.matches
|
|
1271
|
+
|
|
1272
|
+
exitingMatches = previousMatches.filter(
|
|
1273
|
+
(match) => !newMatches.find((d) => d.id === match.id),
|
|
1274
|
+
)
|
|
1275
|
+
enteringMatches = newMatches.filter(
|
|
1276
|
+
(match) => !previousMatches.find((d) => d.id === match.id),
|
|
1277
|
+
)
|
|
1278
|
+
stayingMatches = previousMatches.filter((match) =>
|
|
1279
|
+
newMatches.find((d) => d.id === match.id),
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
return {
|
|
1283
|
+
...s,
|
|
1284
|
+
isLoading: false,
|
|
1285
|
+
matches: newMatches,
|
|
1286
|
+
pendingMatches: undefined,
|
|
1287
|
+
cachedMatches: [
|
|
1288
|
+
...s.cachedMatches,
|
|
1289
|
+
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1290
|
+
],
|
|
1291
|
+
}
|
|
1292
|
+
})
|
|
1293
|
+
this.cleanCache()
|
|
1294
|
+
})
|
|
1295
|
+
|
|
1296
|
+
//
|
|
1297
|
+
;(
|
|
1298
|
+
[
|
|
1299
|
+
[exitingMatches, 'onLeave'],
|
|
1300
|
+
[enteringMatches, 'onEnter'],
|
|
1301
|
+
[stayingMatches, 'onStay'],
|
|
1302
|
+
] as const
|
|
1303
|
+
).forEach(([matches, hook]) => {
|
|
1304
|
+
matches.forEach((match) => {
|
|
1305
|
+
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1306
|
+
})
|
|
1307
|
+
})
|
|
1308
|
+
})
|
|
1309
|
+
},
|
|
1310
|
+
})
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
if (isResolvedRedirect(err)) {
|
|
1313
|
+
redirect = err
|
|
1314
|
+
if (!this.isServer) {
|
|
1315
|
+
this.navigate({ ...err, replace: true })
|
|
1316
|
+
this.load()
|
|
1317
|
+
}
|
|
1318
|
+
} else if (isNotFound(err)) {
|
|
1319
|
+
notFound = err
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
this.__store.setState((s) => ({
|
|
1323
|
+
...s,
|
|
1324
|
+
statusCode:
|
|
1325
|
+
redirect?.statusCode || notFound
|
|
1326
|
+
? 404
|
|
1327
|
+
: s.matches.some((d) => d.status === 'error')
|
|
1328
|
+
? 500
|
|
1329
|
+
: 200,
|
|
1330
|
+
redirect,
|
|
1331
|
+
}))
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
promise.resolve()
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
return this.latestLoadPromise
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
startViewTransition = async (fn: () => Promise<void>) => {
|
|
1341
|
+
// Determine if we should start a view transition from the navigation
|
|
1342
|
+
// or from the router default
|
|
1343
|
+
const shouldViewTransition =
|
|
1344
|
+
this.shouldViewTransition ?? this.options.defaultViewTransition
|
|
1345
|
+
|
|
1346
|
+
// Reset the view transition flag
|
|
1347
|
+
delete this.shouldViewTransition
|
|
1348
|
+
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
1349
|
+
;(shouldViewTransition && typeof document !== 'undefined'
|
|
1350
|
+
? document
|
|
1351
|
+
: undefined
|
|
1352
|
+
)
|
|
1353
|
+
// @ts-expect-error
|
|
1354
|
+
?.startViewTransition?.(fn) || fn()
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1209
1357
|
loadMatches = async ({
|
|
1210
1358
|
checkLatest,
|
|
1211
1359
|
location,
|
|
1212
1360
|
matches,
|
|
1213
1361
|
preload,
|
|
1362
|
+
onReady,
|
|
1214
1363
|
}: {
|
|
1215
|
-
checkLatest: () =>
|
|
1364
|
+
checkLatest: () => void
|
|
1216
1365
|
location: ParsedLocation
|
|
1217
1366
|
matches: Array<AnyRouteMatch>
|
|
1218
1367
|
preload?: boolean
|
|
1368
|
+
onReady?: () => Promise<void>
|
|
1219
1369
|
}): Promise<Array<MakeRouteMatch>> => {
|
|
1220
|
-
let latestPromise
|
|
1221
1370
|
let firstBadMatchIndex: number | undefined
|
|
1371
|
+
let rendered = false
|
|
1372
|
+
|
|
1373
|
+
const triggerOnReady = async () => {
|
|
1374
|
+
if (!rendered) {
|
|
1375
|
+
rendered = true
|
|
1376
|
+
await onReady?.()
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (!this.isServer && !this.state.matches.length) {
|
|
1381
|
+
triggerOnReady()
|
|
1382
|
+
}
|
|
1222
1383
|
|
|
1223
1384
|
const updateMatch = (
|
|
1224
1385
|
id: string,
|
|
@@ -1247,40 +1408,43 @@ export class Router<
|
|
|
1247
1408
|
return updated
|
|
1248
1409
|
}
|
|
1249
1410
|
|
|
1411
|
+
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
1412
|
+
if (isResolvedRedirect(err)) throw err
|
|
1413
|
+
|
|
1414
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
1415
|
+
// if (!rendered) {
|
|
1416
|
+
updateMatch(match.id, (prev) => ({
|
|
1417
|
+
...prev,
|
|
1418
|
+
status: isRedirect(err)
|
|
1419
|
+
? 'redirected'
|
|
1420
|
+
: isNotFound(err)
|
|
1421
|
+
? 'notFound'
|
|
1422
|
+
: 'error',
|
|
1423
|
+
isFetching: false,
|
|
1424
|
+
error: err,
|
|
1425
|
+
}))
|
|
1426
|
+
// }
|
|
1427
|
+
|
|
1428
|
+
rendered = true
|
|
1429
|
+
|
|
1430
|
+
if (!(err as any).routeId) {
|
|
1431
|
+
;(err as any).routeId = match.routeId
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
if (isRedirect(err)) {
|
|
1435
|
+
err = this.resolveRedirect(err)
|
|
1436
|
+
throw err
|
|
1437
|
+
} else if (isNotFound(err)) {
|
|
1438
|
+
this.handleNotFound(matches, err)
|
|
1439
|
+
throw err
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1250
1444
|
try {
|
|
1251
1445
|
await new Promise<void>((resolveAll, rejectAll) => {
|
|
1252
1446
|
;(async () => {
|
|
1253
1447
|
try {
|
|
1254
|
-
const handleRedirectAndNotFound = (
|
|
1255
|
-
match: AnyRouteMatch,
|
|
1256
|
-
err: any,
|
|
1257
|
-
) => {
|
|
1258
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
1259
|
-
updateMatch(match.id, (prev) => ({
|
|
1260
|
-
...prev,
|
|
1261
|
-
status: isRedirect(err)
|
|
1262
|
-
? 'redirected'
|
|
1263
|
-
: isNotFound(err)
|
|
1264
|
-
? 'notFound'
|
|
1265
|
-
: 'error',
|
|
1266
|
-
isFetching: false,
|
|
1267
|
-
error: err,
|
|
1268
|
-
}))
|
|
1269
|
-
|
|
1270
|
-
if (!(err as any).routeId) {
|
|
1271
|
-
;(err as any).routeId = match.routeId
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
if (isRedirect(err)) {
|
|
1275
|
-
err = this.resolveRedirect(err)
|
|
1276
|
-
throw err
|
|
1277
|
-
} else if (isNotFound(err)) {
|
|
1278
|
-
this.handleNotFound(matches, err)
|
|
1279
|
-
throw err
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
1448
|
// Check each match middleware to see if the route can be accessed
|
|
1285
1449
|
// eslint-disable-next-line prefer-const
|
|
1286
1450
|
for (let [index, match] of matches.entries()) {
|
|
@@ -1289,6 +1453,33 @@ export class Router<
|
|
|
1289
1453
|
const abortController = new AbortController()
|
|
1290
1454
|
let loadPromise = match.loadPromise
|
|
1291
1455
|
|
|
1456
|
+
const pendingMs =
|
|
1457
|
+
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1458
|
+
|
|
1459
|
+
const shouldPending = !!(
|
|
1460
|
+
onReady &&
|
|
1461
|
+
!this.isServer &&
|
|
1462
|
+
!preload &&
|
|
1463
|
+
(route.options.loader || route.options.beforeLoad) &&
|
|
1464
|
+
typeof pendingMs === 'number' &&
|
|
1465
|
+
pendingMs !== Infinity &&
|
|
1466
|
+
(route.options.pendingComponent ??
|
|
1467
|
+
this.options.defaultPendingComponent)
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
if (shouldPending) {
|
|
1471
|
+
// If we might show a pending component, we need to wait for the
|
|
1472
|
+
// pending promise to resolve before we start showing that state
|
|
1473
|
+
setTimeout(() => {
|
|
1474
|
+
try {
|
|
1475
|
+
checkLatest()
|
|
1476
|
+
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1477
|
+
// the pending component can start rendering
|
|
1478
|
+
triggerOnReady()
|
|
1479
|
+
} catch {}
|
|
1480
|
+
}, pendingMs)
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1292
1483
|
if (match.isFetching) {
|
|
1293
1484
|
continue
|
|
1294
1485
|
}
|
|
@@ -1336,42 +1527,10 @@ export class Router<
|
|
|
1336
1527
|
handleSerialError(match.searchError, 'VALIDATE_SEARCH')
|
|
1337
1528
|
}
|
|
1338
1529
|
|
|
1339
|
-
// if (match.globalNotFound && !preload) {
|
|
1340
|
-
// handleSerialError(notFound({ _global: true }), 'NOT_FOUND')
|
|
1341
|
-
// }
|
|
1342
|
-
|
|
1343
1530
|
try {
|
|
1344
1531
|
const parentContext =
|
|
1345
1532
|
parentMatch?.context ?? this.options.context ?? {}
|
|
1346
1533
|
|
|
1347
|
-
const pendingMs =
|
|
1348
|
-
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1349
|
-
const pendingPromise =
|
|
1350
|
-
typeof pendingMs !== 'number' || pendingMs <= 0
|
|
1351
|
-
? Promise.resolve()
|
|
1352
|
-
: new Promise<void>((r) => {
|
|
1353
|
-
if (pendingMs !== Infinity) setTimeout(r, pendingMs)
|
|
1354
|
-
})
|
|
1355
|
-
|
|
1356
|
-
const shouldPending =
|
|
1357
|
-
!this.isServer &&
|
|
1358
|
-
!preload &&
|
|
1359
|
-
(route.options.loader || route.options.beforeLoad) &&
|
|
1360
|
-
typeof pendingMs === 'number' &&
|
|
1361
|
-
(route.options.pendingComponent ??
|
|
1362
|
-
this.options.defaultPendingComponent)
|
|
1363
|
-
|
|
1364
|
-
if (shouldPending) {
|
|
1365
|
-
// If we might show a pending component, we need to wait for the
|
|
1366
|
-
// pending promise to resolve before we start showing that state
|
|
1367
|
-
pendingPromise.then(async () => {
|
|
1368
|
-
if ((latestPromise = checkLatest())) return latestPromise
|
|
1369
|
-
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1370
|
-
// the pending component can start rendering
|
|
1371
|
-
resolveAll()
|
|
1372
|
-
})
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
1534
|
const beforeLoadContext =
|
|
1376
1535
|
(await route.options.beforeLoad?.({
|
|
1377
1536
|
search: match.search,
|
|
@@ -1386,7 +1545,7 @@ export class Router<
|
|
|
1386
1545
|
cause: preload ? 'preload' : match.cause,
|
|
1387
1546
|
})) ?? ({} as any)
|
|
1388
1547
|
|
|
1389
|
-
|
|
1548
|
+
checkLatest()
|
|
1390
1549
|
|
|
1391
1550
|
if (
|
|
1392
1551
|
isRedirect(beforeLoadContext) ||
|
|
@@ -1417,7 +1576,7 @@ export class Router<
|
|
|
1417
1576
|
}
|
|
1418
1577
|
}
|
|
1419
1578
|
|
|
1420
|
-
|
|
1579
|
+
checkLatest()
|
|
1421
1580
|
|
|
1422
1581
|
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
1423
1582
|
const matchPromises: Array<Promise<any>> = []
|
|
@@ -1458,8 +1617,7 @@ export class Router<
|
|
|
1458
1617
|
if (latestMatch?.minPendingPromise) {
|
|
1459
1618
|
await latestMatch.minPendingPromise
|
|
1460
1619
|
|
|
1461
|
-
|
|
1462
|
-
return await latestPromise
|
|
1620
|
+
checkLatest()
|
|
1463
1621
|
|
|
1464
1622
|
updateMatch(latestMatch.id, (prev) => ({
|
|
1465
1623
|
...prev,
|
|
@@ -1512,8 +1670,7 @@ export class Router<
|
|
|
1512
1670
|
// we can use the options
|
|
1513
1671
|
await lazyPromise
|
|
1514
1672
|
|
|
1515
|
-
|
|
1516
|
-
return await latestPromise
|
|
1673
|
+
checkLatest()
|
|
1517
1674
|
|
|
1518
1675
|
// Kick off the loader!
|
|
1519
1676
|
loaderPromise = route.options.loader?.(loaderContext)
|
|
@@ -1528,17 +1685,12 @@ export class Router<
|
|
|
1528
1685
|
}
|
|
1529
1686
|
|
|
1530
1687
|
const loaderData = await loaderPromise
|
|
1531
|
-
|
|
1532
|
-
return await latestPromise
|
|
1688
|
+
checkLatest()
|
|
1533
1689
|
|
|
1534
1690
|
handleRedirectAndNotFound(match, loaderData)
|
|
1535
1691
|
|
|
1536
|
-
if ((latestPromise = checkLatest()))
|
|
1537
|
-
return await latestPromise
|
|
1538
|
-
|
|
1539
1692
|
await potentialPendingMinPromise()
|
|
1540
|
-
|
|
1541
|
-
return await latestPromise
|
|
1693
|
+
checkLatest()
|
|
1542
1694
|
|
|
1543
1695
|
const meta = route.options.meta?.({
|
|
1544
1696
|
params: match.params,
|
|
@@ -1560,13 +1712,11 @@ export class Router<
|
|
|
1560
1712
|
headers,
|
|
1561
1713
|
}))
|
|
1562
1714
|
} catch (e) {
|
|
1715
|
+
checkLatest()
|
|
1563
1716
|
let error = e
|
|
1564
|
-
if ((latestPromise = checkLatest()))
|
|
1565
|
-
return await latestPromise
|
|
1566
1717
|
|
|
1567
1718
|
await potentialPendingMinPromise()
|
|
1568
|
-
|
|
1569
|
-
return await latestPromise
|
|
1719
|
+
checkLatest()
|
|
1570
1720
|
|
|
1571
1721
|
handleRedirectAndNotFound(match, e)
|
|
1572
1722
|
|
|
@@ -1589,8 +1739,7 @@ export class Router<
|
|
|
1589
1739
|
// to be preloaded before we resolve the match
|
|
1590
1740
|
await componentsPromise
|
|
1591
1741
|
|
|
1592
|
-
|
|
1593
|
-
return await latestPromise
|
|
1742
|
+
checkLatest()
|
|
1594
1743
|
|
|
1595
1744
|
match.loadPromise.resolve()
|
|
1596
1745
|
}
|
|
@@ -1627,8 +1776,7 @@ export class Router<
|
|
|
1627
1776
|
try {
|
|
1628
1777
|
await fetch()
|
|
1629
1778
|
} catch (err) {
|
|
1630
|
-
|
|
1631
|
-
return await latestPromise
|
|
1779
|
+
checkLatest()
|
|
1632
1780
|
handleRedirectAndNotFound(match, err)
|
|
1633
1781
|
}
|
|
1634
1782
|
}
|
|
@@ -1648,7 +1796,7 @@ export class Router<
|
|
|
1648
1796
|
}),
|
|
1649
1797
|
)
|
|
1650
1798
|
|
|
1651
|
-
|
|
1799
|
+
checkLatest()
|
|
1652
1800
|
|
|
1653
1801
|
resolveAll()
|
|
1654
1802
|
} catch (err) {
|
|
@@ -1656,6 +1804,7 @@ export class Router<
|
|
|
1656
1804
|
}
|
|
1657
1805
|
})()
|
|
1658
1806
|
})
|
|
1807
|
+
await triggerOnReady()
|
|
1659
1808
|
} catch (err) {
|
|
1660
1809
|
if (isRedirect(err) || isNotFound(err)) {
|
|
1661
1810
|
throw err
|
|
@@ -1682,184 +1831,6 @@ export class Router<
|
|
|
1682
1831
|
return this.load()
|
|
1683
1832
|
}
|
|
1684
1833
|
|
|
1685
|
-
load = async (): Promise<void> => {
|
|
1686
|
-
let resolveLoad!: (value: void) => void
|
|
1687
|
-
let rejectLoad!: (reason: any) => void
|
|
1688
|
-
|
|
1689
|
-
const promise = new Promise<void>((resolve, reject) => {
|
|
1690
|
-
resolveLoad = resolve
|
|
1691
|
-
rejectLoad = reject
|
|
1692
|
-
})
|
|
1693
|
-
|
|
1694
|
-
this.latestLoadPromise = promise
|
|
1695
|
-
|
|
1696
|
-
let latestPromise: Promise<void> | undefined | null
|
|
1697
|
-
|
|
1698
|
-
this.startReactTransition(async () => {
|
|
1699
|
-
try {
|
|
1700
|
-
const next = this.latestLocation
|
|
1701
|
-
const prevLocation = this.state.resolvedLocation
|
|
1702
|
-
const pathDidChange = prevLocation.href !== next.href
|
|
1703
|
-
|
|
1704
|
-
// Cancel any pending matches
|
|
1705
|
-
this.cancelMatches()
|
|
1706
|
-
|
|
1707
|
-
this.emit({
|
|
1708
|
-
type: 'onBeforeLoad',
|
|
1709
|
-
fromLocation: prevLocation,
|
|
1710
|
-
toLocation: next,
|
|
1711
|
-
pathChanged: pathDidChange,
|
|
1712
|
-
})
|
|
1713
|
-
|
|
1714
|
-
let pendingMatches!: Array<AnyRouteMatch>
|
|
1715
|
-
const previousMatches = this.state.matches
|
|
1716
|
-
|
|
1717
|
-
this.__store.batch(() => {
|
|
1718
|
-
this.cleanCache()
|
|
1719
|
-
|
|
1720
|
-
// Match the routes
|
|
1721
|
-
pendingMatches = this.matchRoutes(next.pathname, next.search)
|
|
1722
|
-
|
|
1723
|
-
// Ingest the new matches
|
|
1724
|
-
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1725
|
-
this.__store.setState((s) => ({
|
|
1726
|
-
...s,
|
|
1727
|
-
status: 'pending',
|
|
1728
|
-
isLoading: true,
|
|
1729
|
-
location: next,
|
|
1730
|
-
pendingMatches,
|
|
1731
|
-
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1732
|
-
return !pendingMatches.find((e) => e.id === d.id)
|
|
1733
|
-
}),
|
|
1734
|
-
}))
|
|
1735
|
-
})
|
|
1736
|
-
|
|
1737
|
-
let redirect: ResolvedRedirect | undefined
|
|
1738
|
-
let notFound: NotFoundError | undefined
|
|
1739
|
-
|
|
1740
|
-
const loadMatches = () =>
|
|
1741
|
-
this.loadMatches({
|
|
1742
|
-
matches: pendingMatches,
|
|
1743
|
-
location: next,
|
|
1744
|
-
checkLatest: () => this.checkLatest(promise),
|
|
1745
|
-
})
|
|
1746
|
-
|
|
1747
|
-
// If we are on the server or non-first load on the client, await
|
|
1748
|
-
// the loadMatches before transitioning
|
|
1749
|
-
if (previousMatches.length || this.isServer) {
|
|
1750
|
-
try {
|
|
1751
|
-
await loadMatches()
|
|
1752
|
-
} catch (err) {
|
|
1753
|
-
if (isRedirect(err)) {
|
|
1754
|
-
redirect = err as ResolvedRedirect
|
|
1755
|
-
} else if (isNotFound(err)) {
|
|
1756
|
-
notFound = err
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
} else {
|
|
1760
|
-
// For client-only first loads, we need to start the transition
|
|
1761
|
-
// immediately and load the matches in the background
|
|
1762
|
-
loadMatches().catch((err) => {
|
|
1763
|
-
// This also means that we need to handle any redirects
|
|
1764
|
-
// that might happen during the load/transition
|
|
1765
|
-
if (isRedirect(err)) {
|
|
1766
|
-
this.navigate({ ...err, replace: true })
|
|
1767
|
-
}
|
|
1768
|
-
// Because our history listener isn't guaranteed to be mounted
|
|
1769
|
-
// on the first load, we need to manually call load again
|
|
1770
|
-
this.load()
|
|
1771
|
-
})
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
// Only apply the latest transition
|
|
1775
|
-
if ((latestPromise = this.checkLatest(promise))) {
|
|
1776
|
-
return latestPromise
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
const exitingMatches = previousMatches.filter(
|
|
1780
|
-
(match) => !pendingMatches.find((d) => d.id === match.id),
|
|
1781
|
-
)
|
|
1782
|
-
const enteringMatches = pendingMatches.filter(
|
|
1783
|
-
(match) => !previousMatches.find((d) => d.id === match.id),
|
|
1784
|
-
)
|
|
1785
|
-
const stayingMatches = previousMatches.filter((match) =>
|
|
1786
|
-
pendingMatches.find((d) => d.id === match.id),
|
|
1787
|
-
)
|
|
1788
|
-
|
|
1789
|
-
// Determine if we should start a view transition from the navigation
|
|
1790
|
-
// or from the router default
|
|
1791
|
-
const shouldViewTransition =
|
|
1792
|
-
this.shouldViewTransition ?? this.options.defaultViewTransition
|
|
1793
|
-
|
|
1794
|
-
// Reset the view transition flag
|
|
1795
|
-
delete this.shouldViewTransition
|
|
1796
|
-
|
|
1797
|
-
const apply = () => {
|
|
1798
|
-
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1799
|
-
|
|
1800
|
-
// Commit the pending matches. If a previous match was
|
|
1801
|
-
// removed, place it in the cachedMatches
|
|
1802
|
-
this.__store.batch(() => {
|
|
1803
|
-
this.__store.setState((s) => ({
|
|
1804
|
-
...s,
|
|
1805
|
-
isLoading: false,
|
|
1806
|
-
matches: s.pendingMatches!,
|
|
1807
|
-
pendingMatches: undefined,
|
|
1808
|
-
cachedMatches: [
|
|
1809
|
-
...s.cachedMatches,
|
|
1810
|
-
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1811
|
-
],
|
|
1812
|
-
statusCode:
|
|
1813
|
-
redirect?.statusCode || notFound
|
|
1814
|
-
? 404
|
|
1815
|
-
: s.matches.some((d) => d.status === 'error')
|
|
1816
|
-
? 500
|
|
1817
|
-
: 200,
|
|
1818
|
-
redirect,
|
|
1819
|
-
}))
|
|
1820
|
-
this.cleanCache()
|
|
1821
|
-
})
|
|
1822
|
-
|
|
1823
|
-
//
|
|
1824
|
-
;(
|
|
1825
|
-
[
|
|
1826
|
-
[exitingMatches, 'onLeave'],
|
|
1827
|
-
[enteringMatches, 'onEnter'],
|
|
1828
|
-
[stayingMatches, 'onStay'],
|
|
1829
|
-
] as const
|
|
1830
|
-
).forEach(([matches, hook]) => {
|
|
1831
|
-
matches.forEach((match) => {
|
|
1832
|
-
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1833
|
-
})
|
|
1834
|
-
})
|
|
1835
|
-
|
|
1836
|
-
resolveLoad()
|
|
1837
|
-
|
|
1838
|
-
// return this.viewTransitionPromise
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
1842
|
-
;(shouldViewTransition && typeof document !== 'undefined'
|
|
1843
|
-
? document
|
|
1844
|
-
: undefined
|
|
1845
|
-
)
|
|
1846
|
-
// @ts-expect-error
|
|
1847
|
-
?.startViewTransition?.(apply) || apply()
|
|
1848
|
-
} catch (err) {
|
|
1849
|
-
// Only apply the latest transition
|
|
1850
|
-
if ((latestPromise = this.checkLatest(promise))) {
|
|
1851
|
-
return latestPromise
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
console.error('Load Error', err)
|
|
1855
|
-
|
|
1856
|
-
rejectLoad(err)
|
|
1857
|
-
}
|
|
1858
|
-
})
|
|
1859
|
-
|
|
1860
|
-
return this.latestLoadPromise
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
1834
|
resolveRedirect = (err: AnyRedirect): ResolvedRedirect => {
|
|
1864
1835
|
const redirect = err as ResolvedRedirect
|
|
1865
1836
|
|