@tanstack/react-router 1.31.0 → 1.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/router.ts CHANGED
@@ -205,7 +205,7 @@ export interface BuildNextOptions {
205
205
  unmaskOnReload?: boolean
206
206
  }
207
207
  from?: string
208
- _fromLocation?: ParsedLocation
208
+ fromSearch?: unknown
209
209
  }
210
210
 
211
211
  export interface DehydratedRouterState {
@@ -305,7 +305,6 @@ export class Router<
305
305
  )}`
306
306
  resetNextScroll = true
307
307
  shouldViewTransition?: true = undefined
308
- navigateTimeout: Timeout | null = null
309
308
  latestLoadPromise: Promise<void> = Promise.resolve()
310
309
  subscribers = new Set<RouterListener<RouterEvent>>()
311
310
  injectedHtml: Array<InjectedHtmlEntry> = []
@@ -920,25 +919,25 @@ export class Router<
920
919
  } = {},
921
920
  matches?: Array<MakeRouteMatch<TRouteTree>>,
922
921
  ): ParsedLocation => {
923
- const fromPath = dest.from || this.latestLocation.pathname
924
- let fromSearch = dest._fromLocation?.search || this.latestLocation.search
922
+ let fromPath = this.latestLocation.pathname
923
+ let fromSearch = dest.fromSearch || this.latestLocation.search
925
924
 
926
- const fromMatches = this.matchRoutes(fromPath, fromSearch)
925
+ const fromMatches = this.matchRoutes(
926
+ this.latestLocation.pathname,
927
+ fromSearch,
928
+ )
927
929
 
930
+ fromPath =
931
+ fromMatches.find((d) => d.id === dest.from)?.pathname || fromPath
928
932
  fromSearch = last(fromMatches)?.search || this.latestLocation.search
929
933
 
930
934
  const stayingMatches = matches?.filter((d) =>
931
935
  fromMatches.find((e) => e.routeId === d.routeId),
932
936
  )
933
937
 
934
- const fromRoute = this.looseRoutesById[last(fromMatches)?.routeId]
935
-
936
938
  let pathname = dest.to
937
- ? this.resolvePathWithBase(
938
- dest.from ?? this.latestLocation.pathname,
939
- `${dest.to}`,
940
- )
941
- : this.resolvePathWithBase(fromRoute?.fullPath, fromRoute?.fullPath)
939
+ ? this.resolvePathWithBase(fromPath, `${dest.to}`)
940
+ : this.resolvePathWithBase(fromPath, fromPath)
942
941
 
943
942
  const prevParams = { ...last(fromMatches)?.params }
944
943
 
@@ -1113,8 +1112,6 @@ export class Router<
1113
1112
  viewTransition,
1114
1113
  ...next
1115
1114
  }: ParsedLocation & CommitLocationOptions) => {
1116
- if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
1117
-
1118
1115
  const isSameUrl = this.latestLocation.href === next.href
1119
1116
 
1120
1117
  // If the next urls are the same and we're not replacing,
@@ -1275,15 +1272,10 @@ export class Router<
1275
1272
  }
1276
1273
 
1277
1274
  if (isRedirect(err)) {
1278
- const redirect = this.resolveRedirect(err)
1279
-
1280
- if (!preload && !this.isServer) {
1281
- this.navigate({ ...(redirect as any), replace: true })
1282
- }
1283
-
1284
- throw redirect
1275
+ err = this.resolveRedirect(err)
1276
+ throw err
1285
1277
  } else if (isNotFound(err)) {
1286
- if (!preload) this.handleNotFound(matches, err)
1278
+ this.handleNotFound(matches, err)
1287
1279
  throw err
1288
1280
  }
1289
1281
  }
@@ -1295,6 +1287,25 @@ export class Router<
1295
1287
  const parentMatch = matches[index - 1]
1296
1288
  const route = this.looseRoutesById[match.routeId]!
1297
1289
  const abortController = new AbortController()
1290
+ let loadPromise = match.loadPromise
1291
+
1292
+ if (match.isFetching) {
1293
+ continue
1294
+ }
1295
+
1296
+ const previousResolve = loadPromise.resolve
1297
+ // Create a new one
1298
+ loadPromise = createControlledPromise<void>(
1299
+ // Resolve the old when we we resolve the new one
1300
+ previousResolve,
1301
+ )
1302
+
1303
+ // Otherwise, load the route
1304
+ matches[index] = match = updateMatch(match.id, (prev) => ({
1305
+ ...prev,
1306
+ isFetching: 'beforeLoad',
1307
+ loadPromise,
1308
+ }))
1298
1309
 
1299
1310
  const handleSerialError = (err: any, routerCode: string) => {
1300
1311
  err.routerCode = routerCode
@@ -1375,6 +1386,8 @@ export class Router<
1375
1386
  cause: preload ? 'preload' : match.cause,
1376
1387
  })) ?? ({} as any)
1377
1388
 
1389
+ if ((latestPromise = checkLatest())) return latestPromise
1390
+
1378
1391
  if (
1379
1392
  isRedirect(beforeLoadContext) ||
1380
1393
  isNotFound(beforeLoadContext)
@@ -1404,6 +1417,8 @@ export class Router<
1404
1417
  }
1405
1418
  }
1406
1419
 
1420
+ if ((latestPromise = checkLatest())) return latestPromise
1421
+
1407
1422
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
1408
1423
  const matchPromises: Array<Promise<any>> = []
1409
1424
 
@@ -1431,7 +1446,6 @@ export class Router<
1431
1446
  let lazyPromise = Promise.resolve()
1432
1447
  let componentsPromise = Promise.resolve() as Promise<any>
1433
1448
  let loaderPromise = existing.loaderPromise
1434
- let loadPromise = existing.loadPromise
1435
1449
 
1436
1450
  // If the Matches component rendered
1437
1451
  // the pending component and needs to show it for
@@ -1455,7 +1469,7 @@ export class Router<
1455
1469
  }
1456
1470
 
1457
1471
  try {
1458
- if (!match.isFetching) {
1472
+ if (match.isFetching === 'beforeLoad') {
1459
1473
  // If the user doesn't want the route to reload, just
1460
1474
  // resolve with the existing loader data
1461
1475
 
@@ -1464,11 +1478,14 @@ export class Router<
1464
1478
  // }
1465
1479
 
1466
1480
  // Otherwise, load the route
1467
- matches[index] = match = {
1468
- ...match,
1469
- isFetching: true,
1470
- fetchCount: match.fetchCount + 1,
1471
- }
1481
+ matches[index] = match = updateMatch(
1482
+ match.id,
1483
+ (prev) => ({
1484
+ ...prev,
1485
+ isFetching: 'loader',
1486
+ fetchCount: match.fetchCount + 1,
1487
+ }),
1488
+ )
1472
1489
 
1473
1490
  lazyPromise =
1474
1491
  route.lazyFn?.().then((lazyRoute) => {
@@ -1501,20 +1518,15 @@ export class Router<
1501
1518
  // Kick off the loader!
1502
1519
  loaderPromise = route.options.loader?.(loaderContext)
1503
1520
 
1504
- const previousResolve = loadPromise.resolve
1505
- // Create a new one
1506
- loadPromise = createControlledPromise<void>(
1507
- // Resolve the old when we we resolve the new one
1508
- previousResolve,
1521
+ matches[index] = match = updateMatch(
1522
+ match.id,
1523
+ (prev) => ({
1524
+ ...prev,
1525
+ loaderPromise,
1526
+ }),
1509
1527
  )
1510
1528
  }
1511
1529
 
1512
- matches[index] = match = updateMatch(match.id, (prev) => ({
1513
- ...prev,
1514
- loaderPromise,
1515
- loadPromise,
1516
- }))
1517
-
1518
1530
  const loaderData = await loaderPromise
1519
1531
  if ((latestPromise = checkLatest()))
1520
1532
  return await latestPromise
@@ -1580,7 +1592,7 @@ export class Router<
1580
1592
  if ((latestPromise = checkLatest()))
1581
1593
  return await latestPromise
1582
1594
 
1583
- loadPromise.resolve()
1595
+ match.loadPromise.resolve()
1584
1596
  }
1585
1597
 
1586
1598
  // This is where all of the stale-while-revalidate magic happens
@@ -1725,27 +1737,38 @@ export class Router<
1725
1737
  let redirect: ResolvedRedirect | undefined
1726
1738
  let notFound: NotFoundError | undefined
1727
1739
 
1728
- try {
1729
- // Load the matches
1730
- const loadMatchesPromise = this.loadMatches({
1740
+ const loadMatches = () =>
1741
+ this.loadMatches({
1731
1742
  matches: pendingMatches,
1732
1743
  location: next,
1733
1744
  checkLatest: () => this.checkLatest(promise),
1734
1745
  })
1735
1746
 
1736
- if (previousMatches.length || this.isServer) {
1737
- await loadMatchesPromise
1738
- }
1739
- } catch (err) {
1740
- if (isRedirect(err)) {
1741
- redirect = err as ResolvedRedirect
1742
- } else if (isNotFound(err)) {
1743
- notFound = err
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
+ }
1744
1758
  }
1745
-
1746
- // Swallow all other errors that happen inside
1747
- // of loadMatches. These errors will be handled
1748
- // as state on each match.
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
+ })
1749
1772
  }
1750
1773
 
1751
1774
  // Only apply the latest transition
@@ -1942,7 +1965,7 @@ export class Router<
1942
1965
  } catch (err) {
1943
1966
  if (isRedirect(err)) {
1944
1967
  return await this.preloadRoute({
1945
- _fromDest: next,
1968
+ fromSearch: next.search,
1946
1969
  from: next.pathname,
1947
1970
  ...(err as any),
1948
1971
  })