@tanstack/react-router 1.16.2 → 1.16.6
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/fileRoute.cjs.map +1 -1
- package/dist/cjs/fileRoute.d.cts +4 -2
- package/dist/cjs/link.cjs +16 -16
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/not-found.d.cts +1 -1
- package/dist/cjs/redirects.cjs.map +1 -1
- package/dist/cjs/redirects.d.cts +1 -0
- package/dist/cjs/route.cjs +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +4 -3
- package/dist/cjs/router.cjs +142 -135
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +4 -2
- package/dist/esm/fileRoute.d.ts +4 -2
- package/dist/esm/fileRoute.js.map +1 -1
- package/dist/esm/link.js +16 -16
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/not-found.d.ts +1 -1
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/redirects.d.ts +1 -0
- package/dist/esm/redirects.js.map +1 -1
- package/dist/esm/route.d.ts +4 -3
- package/dist/esm/route.js +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +4 -2
- package/dist/esm/router.js +143 -136
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/fileRoute.ts +2 -2
- package/src/link.tsx +18 -18
- package/src/not-found.tsx +1 -1
- package/src/redirects.ts +1 -0
- package/src/route.ts +11 -3
- package/src/router.ts +167 -164
package/src/router.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
last,
|
|
39
39
|
pick,
|
|
40
40
|
Timeout,
|
|
41
|
+
isServer,
|
|
41
42
|
} from './utils'
|
|
42
43
|
import { RouteComponent } from './route'
|
|
43
44
|
import { AnyRouteMatch, MatchRouteOptions, RouteMatch } from './Matches'
|
|
@@ -63,19 +64,12 @@ import {
|
|
|
63
64
|
trimPathRight,
|
|
64
65
|
} from './path'
|
|
65
66
|
import invariant from 'tiny-invariant'
|
|
66
|
-
import { isRedirect } from './redirects'
|
|
67
|
+
import { AnyRedirect, isRedirect } from './redirects'
|
|
67
68
|
import { NotFoundError, isNotFound } from './not-found'
|
|
68
69
|
import { ResolveRelativePath, ToOptions } from './link'
|
|
69
70
|
import { NoInfer } from '@tanstack/react-store'
|
|
70
71
|
import warning from 'tiny-warning'
|
|
71
|
-
import {
|
|
72
|
-
DeferredPromise,
|
|
73
|
-
DeferredPromiseState,
|
|
74
|
-
defaultDeserializeError,
|
|
75
|
-
isDehydratedDeferred,
|
|
76
|
-
isServerSideError,
|
|
77
|
-
} from '.'
|
|
78
|
-
// import warning from 'tiny-warning'
|
|
72
|
+
import { DeferredPromiseState } from '.'
|
|
79
73
|
|
|
80
74
|
//
|
|
81
75
|
|
|
@@ -1135,108 +1129,96 @@ export class Router<
|
|
|
1135
1129
|
}
|
|
1136
1130
|
|
|
1137
1131
|
// Check each match middleware to see if the route can be accessed
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const abortController = new AbortController()
|
|
1143
|
-
|
|
1144
|
-
const handleErrorAndRedirect = (err: any, code: string) => {
|
|
1145
|
-
err.routerCode = code
|
|
1146
|
-
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
1132
|
+
for (let [index, match] of matches.entries()) {
|
|
1133
|
+
const parentMatch = matches[index - 1]
|
|
1134
|
+
const route = this.looseRoutesById[match.routeId]!
|
|
1135
|
+
const abortController = new AbortController()
|
|
1147
1136
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1137
|
+
const handleError = (err: any, code: string) => {
|
|
1138
|
+
err.routerCode = code
|
|
1139
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
1151
1140
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1141
|
+
if (isRedirect(err)) {
|
|
1142
|
+
throw err
|
|
1143
|
+
}
|
|
1155
1144
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1145
|
+
if (isNotFound(err)) {
|
|
1146
|
+
err.routeId = match.routeId
|
|
1147
|
+
throw err
|
|
1148
|
+
}
|
|
1160
1149
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1150
|
+
try {
|
|
1151
|
+
route.options.onError?.(err)
|
|
1152
|
+
} catch (errorHandlerErr) {
|
|
1153
|
+
err = errorHandlerErr
|
|
1165
1154
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
error: err,
|
|
1169
|
-
status: 'error',
|
|
1170
|
-
updatedAt: Date.now(),
|
|
1171
|
-
abortController: new AbortController(),
|
|
1155
|
+
if (isRedirect(errorHandlerErr)) {
|
|
1156
|
+
throw errorHandlerErr
|
|
1172
1157
|
}
|
|
1173
1158
|
}
|
|
1174
1159
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1160
|
+
matches[index] = match = {
|
|
1161
|
+
...match,
|
|
1162
|
+
error: err,
|
|
1163
|
+
status: 'error',
|
|
1164
|
+
updatedAt: Date.now(),
|
|
1165
|
+
abortController: new AbortController(),
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1179
1168
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1169
|
+
try {
|
|
1170
|
+
if (match.paramsError) {
|
|
1171
|
+
handleError(match.paramsError, 'PARSE_PARAMS')
|
|
1172
|
+
}
|
|
1183
1173
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1174
|
+
if (match.searchError) {
|
|
1175
|
+
handleError(match.searchError, 'VALIDATE_SEARCH')
|
|
1176
|
+
}
|
|
1186
1177
|
|
|
1187
|
-
|
|
1188
|
-
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1189
|
-
const pendingPromise =
|
|
1190
|
-
typeof pendingMs === 'number' && pendingMs <= 0
|
|
1191
|
-
? Promise.resolve()
|
|
1192
|
-
: new Promise<void>((r) => setTimeout(r, pendingMs))
|
|
1193
|
-
|
|
1194
|
-
const beforeLoadContext =
|
|
1195
|
-
(await route.options.beforeLoad?.({
|
|
1196
|
-
search: match.search,
|
|
1197
|
-
abortController,
|
|
1198
|
-
params: match.params,
|
|
1199
|
-
preload: !!preload,
|
|
1200
|
-
context: parentContext,
|
|
1201
|
-
location: this.state.location,
|
|
1202
|
-
// TOOD: just expose state and router, etc
|
|
1203
|
-
navigate: (opts) =>
|
|
1204
|
-
this.navigate({ ...opts, from: match.pathname } as any),
|
|
1205
|
-
buildLocation: this.buildLocation,
|
|
1206
|
-
cause: preload ? 'preload' : match.cause,
|
|
1207
|
-
})) ?? ({} as any)
|
|
1208
|
-
|
|
1209
|
-
if (isRedirect(beforeLoadContext)) {
|
|
1210
|
-
throw beforeLoadContext
|
|
1211
|
-
}
|
|
1178
|
+
const parentContext = parentMatch?.context ?? this.options.context ?? {}
|
|
1212
1179
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1180
|
+
const pendingMs =
|
|
1181
|
+
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1182
|
+
const pendingPromise =
|
|
1183
|
+
typeof pendingMs === 'number' && pendingMs <= 0
|
|
1184
|
+
? Promise.resolve()
|
|
1185
|
+
: new Promise<void>((r) => setTimeout(r, pendingMs))
|
|
1217
1186
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
match.routeContext,
|
|
1222
|
-
beforeLoadContext,
|
|
1223
|
-
),
|
|
1224
|
-
context: replaceEqualDeep(match.context, context),
|
|
1187
|
+
const beforeLoadContext =
|
|
1188
|
+
(await route.options.beforeLoad?.({
|
|
1189
|
+
search: match.search,
|
|
1225
1190
|
abortController,
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1191
|
+
params: match.params,
|
|
1192
|
+
preload: !!preload,
|
|
1193
|
+
context: parentContext,
|
|
1194
|
+
location: this.state.location,
|
|
1195
|
+
// TOOD: just expose state and router, etc
|
|
1196
|
+
navigate: (opts) =>
|
|
1197
|
+
this.navigate({ ...opts, from: match.pathname } as any),
|
|
1198
|
+
buildLocation: this.buildLocation,
|
|
1199
|
+
cause: preload ? 'preload' : match.cause,
|
|
1200
|
+
})) ?? ({} as any)
|
|
1201
|
+
|
|
1202
|
+
if (isRedirect(beforeLoadContext)) {
|
|
1203
|
+
throw beforeLoadContext
|
|
1231
1204
|
}
|
|
1232
|
-
}
|
|
1233
|
-
} catch (err) {
|
|
1234
|
-
if (isRedirect(err)) {
|
|
1235
|
-
if (!preload) this.navigate(err as any)
|
|
1236
|
-
return matches
|
|
1237
|
-
}
|
|
1238
1205
|
|
|
1239
|
-
|
|
1206
|
+
const context = {
|
|
1207
|
+
...parentContext,
|
|
1208
|
+
...beforeLoadContext,
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
matches[index] = match = {
|
|
1212
|
+
...match,
|
|
1213
|
+
routeContext: replaceEqualDeep(match.routeContext, beforeLoadContext),
|
|
1214
|
+
context: replaceEqualDeep(match.context, context),
|
|
1215
|
+
abortController,
|
|
1216
|
+
pendingPromise,
|
|
1217
|
+
}
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
handleError(err, 'BEFORE_LOAD')
|
|
1220
|
+
break
|
|
1221
|
+
}
|
|
1240
1222
|
}
|
|
1241
1223
|
|
|
1242
1224
|
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
@@ -1244,26 +1226,19 @@ export class Router<
|
|
|
1244
1226
|
|
|
1245
1227
|
validResolvedMatches.forEach((match, index) => {
|
|
1246
1228
|
matchPromises.push(
|
|
1247
|
-
new Promise<void>(async (resolve) => {
|
|
1229
|
+
new Promise<void>(async (resolve, reject) => {
|
|
1248
1230
|
const parentMatchPromise = matchPromises[index - 1]
|
|
1249
1231
|
const route = this.looseRoutesById[match.routeId]!
|
|
1250
1232
|
|
|
1251
|
-
const
|
|
1233
|
+
const handleError = (err: any) => {
|
|
1252
1234
|
if (isRedirect(err)) {
|
|
1253
|
-
|
|
1254
|
-
this.navigate(err as any)
|
|
1255
|
-
}
|
|
1256
|
-
return true
|
|
1235
|
+
throw err
|
|
1257
1236
|
}
|
|
1258
1237
|
|
|
1259
1238
|
if (isNotFound(err)) {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
return true
|
|
1239
|
+
err.routeId = match.routeId
|
|
1240
|
+
throw err
|
|
1264
1241
|
}
|
|
1265
|
-
|
|
1266
|
-
return false
|
|
1267
1242
|
}
|
|
1268
1243
|
|
|
1269
1244
|
let loadPromise: Promise<void> | undefined
|
|
@@ -1360,9 +1335,7 @@ export class Router<
|
|
|
1360
1335
|
const loaderData = await loadPromise
|
|
1361
1336
|
if ((latestPromise = checkLatest())) return await latestPromise
|
|
1362
1337
|
|
|
1363
|
-
|
|
1364
|
-
if (handleErrorAndRedirect(loaderData)) return
|
|
1365
|
-
}
|
|
1338
|
+
handleError(loaderData)
|
|
1366
1339
|
|
|
1367
1340
|
if (didShowPending && pendingMinMs) {
|
|
1368
1341
|
await new Promise((r) => setTimeout(r, pendingMinMs))
|
|
@@ -1372,6 +1345,7 @@ export class Router<
|
|
|
1372
1345
|
|
|
1373
1346
|
const [meta, headers] = await Promise.all([
|
|
1374
1347
|
route.options.meta?.({
|
|
1348
|
+
params: match.params,
|
|
1375
1349
|
loaderData,
|
|
1376
1350
|
}),
|
|
1377
1351
|
route.options.headers?.({
|
|
@@ -1392,13 +1366,13 @@ export class Router<
|
|
|
1392
1366
|
}
|
|
1393
1367
|
} catch (error) {
|
|
1394
1368
|
if ((latestPromise = checkLatest())) return await latestPromise
|
|
1395
|
-
|
|
1369
|
+
handleError(error)
|
|
1396
1370
|
|
|
1397
1371
|
try {
|
|
1398
1372
|
route.options.onError?.(error)
|
|
1399
1373
|
} catch (onErrorError) {
|
|
1400
1374
|
error = onErrorError
|
|
1401
|
-
|
|
1375
|
+
handleError(onErrorError)
|
|
1402
1376
|
}
|
|
1403
1377
|
|
|
1404
1378
|
matches[index] = match = {
|
|
@@ -1439,29 +1413,33 @@ export class Router<
|
|
|
1439
1413
|
!!preload && !this.state.matches.find((d) => d.id === match.id),
|
|
1440
1414
|
}
|
|
1441
1415
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1416
|
+
try {
|
|
1417
|
+
if (match.status !== 'success') {
|
|
1418
|
+
// If we need to potentially show the pending component,
|
|
1419
|
+
// start a timer to show it after the pendingMs
|
|
1420
|
+
if (shouldPending) {
|
|
1421
|
+
match.pendingPromise?.then(async () => {
|
|
1422
|
+
if ((latestPromise = checkLatest())) return latestPromise
|
|
1423
|
+
|
|
1424
|
+
didShowPending = true
|
|
1425
|
+
matches[index] = match = {
|
|
1426
|
+
...match,
|
|
1427
|
+
showPending: true,
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
updateMatch(match)
|
|
1431
|
+
resolve()
|
|
1432
|
+
})
|
|
1433
|
+
}
|
|
1454
1434
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1435
|
+
// Critical Fetching, we need to await
|
|
1436
|
+
await fetch()
|
|
1437
|
+
} else if (match.invalid || (shouldReload ?? age > staleAge)) {
|
|
1438
|
+
// Background Fetching, no need to wait
|
|
1439
|
+
fetch()
|
|
1458
1440
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
await fetch()
|
|
1462
|
-
} else if (match.invalid || (shouldReload ?? age > staleAge)) {
|
|
1463
|
-
// Background Fetching, no need to wait
|
|
1464
|
-
fetch()
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
reject(err)
|
|
1465
1443
|
}
|
|
1466
1444
|
|
|
1467
1445
|
resolve()
|
|
@@ -1470,6 +1448,7 @@ export class Router<
|
|
|
1470
1448
|
})
|
|
1471
1449
|
|
|
1472
1450
|
await Promise.all(matchPromises)
|
|
1451
|
+
|
|
1473
1452
|
return matches
|
|
1474
1453
|
}
|
|
1475
1454
|
|
|
@@ -1538,8 +1517,11 @@ export class Router<
|
|
|
1538
1517
|
checkLatest: () => this.checkLatest(promise),
|
|
1539
1518
|
})
|
|
1540
1519
|
} catch (err) {
|
|
1541
|
-
|
|
1542
|
-
|
|
1520
|
+
if (isRedirect(err)) {
|
|
1521
|
+
this.handleRedirect(err)
|
|
1522
|
+
} else if (isNotFound(err)) {
|
|
1523
|
+
this.handleNotFound(pendingMatches, err)
|
|
1524
|
+
}
|
|
1543
1525
|
}
|
|
1544
1526
|
|
|
1545
1527
|
// Only apply the latest transition
|
|
@@ -1609,6 +1591,18 @@ export class Router<
|
|
|
1609
1591
|
return this.latestLoadPromise
|
|
1610
1592
|
}
|
|
1611
1593
|
|
|
1594
|
+
handleRedirect = (err: AnyRedirect) => {
|
|
1595
|
+
if (!err.href) {
|
|
1596
|
+
err.href = this.buildLocation(err as any).href
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
if (isServer) {
|
|
1600
|
+
throw err
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
this.navigate(err as any)
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1612
1606
|
cleanCache = () => {
|
|
1613
1607
|
// This is where all of the garbage collection magic happens
|
|
1614
1608
|
this.__store.setState((s) => {
|
|
@@ -1663,13 +1657,22 @@ export class Router<
|
|
|
1663
1657
|
})
|
|
1664
1658
|
})
|
|
1665
1659
|
|
|
1666
|
-
|
|
1667
|
-
matches
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1660
|
+
try {
|
|
1661
|
+
matches = await this.loadMatches({
|
|
1662
|
+
matches,
|
|
1663
|
+
preload: true,
|
|
1664
|
+
checkLatest: () => undefined,
|
|
1665
|
+
})
|
|
1671
1666
|
|
|
1672
|
-
|
|
1667
|
+
return matches
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
// Preload errors are not fatal, but we should still log them
|
|
1670
|
+
if (!isRedirect(err) && !isNotFound(err)) {
|
|
1671
|
+
console.error(err)
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
return undefined
|
|
1675
|
+
}
|
|
1673
1676
|
}
|
|
1674
1677
|
|
|
1675
1678
|
matchRoute = <
|
|
@@ -1856,6 +1859,7 @@ export class Router<
|
|
|
1856
1859
|
...match,
|
|
1857
1860
|
...dehydratedMatch,
|
|
1858
1861
|
meta: route.options.meta?.({
|
|
1862
|
+
params: match.params,
|
|
1859
1863
|
loaderData: dehydratedMatch.loaderData,
|
|
1860
1864
|
}),
|
|
1861
1865
|
links: route.options.links?.(),
|
|
@@ -1875,39 +1879,38 @@ export class Router<
|
|
|
1875
1879
|
}
|
|
1876
1880
|
|
|
1877
1881
|
// Finds a match that has a notFoundComponent
|
|
1878
|
-
|
|
1879
|
-
matches: AnyRouteMatch[],
|
|
1880
|
-
currentMatch: AnyRouteMatch,
|
|
1881
|
-
err: NotFoundError,
|
|
1882
|
-
) => {
|
|
1882
|
+
handleNotFound = (matches: AnyRouteMatch[], err: NotFoundError) => {
|
|
1883
1883
|
const matchesByRouteId = Object.fromEntries(
|
|
1884
1884
|
matches.map((match) => [match.routeId, match]),
|
|
1885
1885
|
) as Record<string, AnyRouteMatch>
|
|
1886
1886
|
|
|
1887
|
-
if (err.global) {
|
|
1888
|
-
matchesByRouteId[rootRouteId]!.notFoundError = err
|
|
1889
|
-
} else {
|
|
1887
|
+
if (!err.global && err.routeId) {
|
|
1890
1888
|
// If the err contains a routeId, start searching up from that route
|
|
1891
|
-
let currentRoute =
|
|
1892
|
-
err.route ?? currentMatch.routeId
|
|
1893
|
-
] as AnyRoute
|
|
1889
|
+
let currentRoute = this.looseRoutesById[err.routeId]
|
|
1894
1890
|
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
currentRoute
|
|
1891
|
+
if (currentRoute) {
|
|
1892
|
+
// Go up the tree until we find a route with a notFoundComponent
|
|
1893
|
+
while (!currentRoute.options.notFoundComponent) {
|
|
1894
|
+
currentRoute = currentRoute?.parentRoute
|
|
1898
1895
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1896
|
+
invariant(
|
|
1897
|
+
currentRoute,
|
|
1898
|
+
'Found invalid route tree while trying to find not-found handler.',
|
|
1899
|
+
)
|
|
1903
1900
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1901
|
+
if (currentRoute.id === rootRouteId) break
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
const match = matchesByRouteId[currentRoute.id]
|
|
1905
|
+
invariant(match, 'Could not find match for route: ' + currentRoute.id)
|
|
1906
|
+
match.notFoundError = err
|
|
1906
1907
|
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
match.notFoundError = err
|
|
1908
|
+
return
|
|
1909
|
+
}
|
|
1910
1910
|
}
|
|
1911
|
+
|
|
1912
|
+
// Otherwise, just set the notFoundError on the root route
|
|
1913
|
+
matchesByRouteId[rootRouteId]!.notFoundError = err
|
|
1911
1914
|
}
|
|
1912
1915
|
|
|
1913
1916
|
hasNotFoundMatch = () => {
|