@tanstack/router-core 0.0.1-beta.174 → 0.0.1-beta.176

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.174",
4
+ "version": "0.0.1-beta.176",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -42,8 +42,8 @@
42
42
  "@babel/runtime": "^7.16.7",
43
43
  "tiny-invariant": "^1.3.1",
44
44
  "tiny-warning": "^1.0.3",
45
- "@gisatcz/cross-package-react-context": "^0.2.0",
46
- "@tanstack/react-store": "0.0.1-beta.174"
45
+ "@tanstack/store": "^0.0.1",
46
+ "@gisatcz/cross-package-react-context": "^0.2.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "rollup --config rollup.config.js",
package/src/route.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import { ParsePathParams } from './link'
2
- import { AnyRouter, Router, RouteMatch, RegisteredRouter } from './router'
2
+ import {
3
+ AnyRouter,
4
+ Router,
5
+ RouteMatch,
6
+ RegisteredRouter,
7
+ AnyRouteMatch,
8
+ } from './router'
3
9
  import { IsAny, NoInfer, PickRequired, UnionToIntersection } from './utils'
4
10
  import invariant from 'tiny-invariant'
5
11
  import { joinPaths, trimPath } from './path'
@@ -386,21 +392,11 @@ export type UpdatableRouteOptions<
386
392
  >,
387
393
  ) => Promise<void> | void
388
394
  onError?: (err: any) => void
389
- // This function is called
390
- // when moving from an inactive state to an active one. Likewise, when moving from
391
- // an active to an inactive state, the return function (if provided) is called.
392
- onLoaded?: (matchContext: {
393
- params: TAllParams
394
- search: TFullSearchSchema
395
- }) =>
396
- | void
397
- | undefined
398
- | ((match: { params: TAllParams; search: TFullSearchSchema }) => void)
399
- // This function is called when the route remains active from one transition to the next.
400
- onTransition?: (match: {
401
- params: TAllParams
402
- search: TFullSearchSchema
403
- }) => void
395
+ // These functions are called as route matches are loaded, stick around and leave the active
396
+ // matches
397
+ onEnter?: (match: AnyRouteMatch) => void
398
+ onTransition?: (match: AnyRouteMatch) => void
399
+ onLeave?: (match: AnyRouteMatch) => void
404
400
  }
405
401
 
406
402
  export type ParseParamsOption<TPath extends string, TParams> = ParseParamsFn<
package/src/router.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Store } from '@tanstack/react-store'
1
+ import { Store } from '@tanstack/store'
2
2
  import invariant from 'tiny-invariant'
3
3
 
4
4
  //
@@ -123,8 +123,8 @@ export interface RouteMatch<
123
123
  paramsError: unknown
124
124
  searchError: unknown
125
125
  updatedAt: number
126
- invalidAt: number
127
- preloadInvalidAt: number
126
+ maxAge: number
127
+ preloadMaxAge: number
128
128
  loaderData: RouteById<TRouteTree, TRouteId>['types']['loader']
129
129
  loadPromise?: Promise<void>
130
130
  __resolveLoadPromise?: () => void
@@ -157,6 +157,7 @@ export interface RouterOptions<
157
157
  parseSearch?: SearchParser
158
158
  defaultPreload?: false | 'intent'
159
159
  defaultPreloadDelay?: number
160
+ refetchOnWindowFocus?: boolean
160
161
  defaultComponent?: RegisteredRouteComponent<
161
162
  unknown,
162
163
  AnySearchSchema,
@@ -249,7 +250,8 @@ export type DehydratedRouteMatch = Pick<
249
250
  RouteMatch,
250
251
  | 'fetchedAt'
251
252
  | 'invalid'
252
- | 'invalidAt'
253
+ | 'maxAge'
254
+ | 'preloadMaxAge'
253
255
  | 'id'
254
256
  | 'loaderData'
255
257
  | 'status'
@@ -294,6 +296,9 @@ export type RouterListener<TRouterEvent extends RouterEvent> = {
294
296
  fn: ListenerFn<TRouterEvent>
295
297
  }
296
298
 
299
+ const visibilityChangeEvent = 'visibilitychange'
300
+ const focusEvent = 'focus'
301
+
297
302
  export class Router<
298
303
  TRouteTree extends AnyRoute = AnyRoute,
299
304
  TDehydrated extends Record<string, any> = Record<string, any>,
@@ -420,10 +425,28 @@ export class Router<
420
425
  }
421
426
 
422
427
  mount = () => {
423
- // If the router matches are empty, start loading the matches
424
- // if (!this.state.matches.length) {
428
+ // addEventListener does not exist in React Native, but window does
429
+ // In the future, we might need to invert control here for more adapters
430
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
431
+ if (typeof window !== 'undefined' && window.addEventListener) {
432
+ window.addEventListener(visibilityChangeEvent, this.#onFocus, false)
433
+ window.addEventListener(focusEvent, this.#onFocus, false)
434
+ }
435
+
425
436
  this.safeLoad()
426
- // }
437
+
438
+ return () => {
439
+ if (typeof window !== 'undefined' && window.removeEventListener) {
440
+ window.removeEventListener(visibilityChangeEvent, this.#onFocus)
441
+ window.removeEventListener(focusEvent, this.#onFocus)
442
+ }
443
+ }
444
+ }
445
+
446
+ #onFocus = () => {
447
+ if (this.options.refetchOnWindowFocus ?? true) {
448
+ this.reload()
449
+ }
427
450
  }
428
451
 
429
452
  update = (opts?: RouterOptions<any, any>): this => {
@@ -525,7 +548,6 @@ export class Router<
525
548
  }
526
549
 
527
550
  // Cancel any pending matches
528
- // this.cancelMatches()
529
551
 
530
552
  let pendingMatches!: RouteMatch<any, any>[]
531
553
 
@@ -577,6 +599,18 @@ export class Router<
577
599
  return latestPromise
578
600
  }
579
601
 
602
+ const exitingMatchIds = this.state.matchIds.filter(
603
+ (id) => !this.state.pendingMatchIds.includes(id),
604
+ )
605
+
606
+ const enteringMatchIds = this.state.pendingMatchIds.filter(
607
+ (id) => !this.state.matchIds.includes(id),
608
+ )
609
+
610
+ const stayingMatchIds = this.state.matchIds.filter((id) =>
611
+ this.state.pendingMatchIds.includes(id),
612
+ )
613
+
580
614
  this.__store.setState((s) => ({
581
615
  ...s,
582
616
  status: 'idle',
@@ -584,6 +618,19 @@ export class Router<
584
618
  matchIds: s.pendingMatchIds,
585
619
  pendingMatchIds: [],
586
620
  }))
621
+ ;(
622
+ [
623
+ [exitingMatchIds, 'onLeave'],
624
+ [enteringMatchIds, 'onEnter'],
625
+ [stayingMatchIds, 'onTransition'],
626
+ ] as const
627
+ ).forEach(([matchIds, hook]) => {
628
+ matchIds.forEach((id) => {
629
+ const match = this.getRouteMatch(id)!
630
+ const route = this.getRoute(match.routeId)
631
+ route.options[hook]?.(match)
632
+ })
633
+ })
587
634
 
588
635
  this.#emit({
589
636
  type: 'onLoad',
@@ -605,6 +652,10 @@ export class Router<
605
652
 
606
653
  this.latestLoadPromise = promise
607
654
 
655
+ this.latestLoadPromise.then(() => {
656
+ this.cleanMatches()
657
+ })
658
+
608
659
  return this.latestLoadPromise
609
660
  }
610
661
 
@@ -671,10 +722,13 @@ export class Router<
671
722
  const outdatedMatchIds = Object.values(this.state.matchesById)
672
723
  .filter((match) => {
673
724
  const route = this.getRoute(match.routeId)
725
+
674
726
  return (
675
727
  !this.state.matchIds.includes(match.id) &&
676
728
  !this.state.pendingMatchIds.includes(match.id) &&
677
- match.preloadInvalidAt < now &&
729
+ (match.preloadMaxAge > -1
730
+ ? match.updatedAt + match.preloadMaxAge < now
731
+ : true) &&
678
732
  (route.options.gcMaxAge
679
733
  ? match.updatedAt + route.options.gcMaxAge < now
680
734
  : true)
@@ -795,8 +849,8 @@ export class Router<
795
849
  params: routeParams,
796
850
  pathname: joinPaths([this.basepath, interpolatedPath]),
797
851
  updatedAt: Date.now(),
798
- invalidAt: Infinity,
799
- preloadInvalidAt: Infinity,
852
+ maxAge: -1,
853
+ preloadMaxAge: -1,
800
854
  routeSearch: {},
801
855
  search: {} as any,
802
856
  status: hasLoaders ? 'pending' : 'success',
@@ -917,13 +971,11 @@ export class Router<
917
971
  paramsError: match.paramsError,
918
972
  searchError: match.searchError,
919
973
  params: match.params,
920
- preloadInvalidAt: 0,
974
+ preloadMaxAge: 0,
921
975
  }))
922
976
  })
923
977
  }
924
978
 
925
- this.cleanMatches()
926
-
927
979
  let firstBadMatchIndex: number | undefined
928
980
 
929
981
  // Check each match middleware to see if the route can be accessed
@@ -1002,7 +1054,9 @@ export class Router<
1002
1054
  if (
1003
1055
  match.isFetching ||
1004
1056
  (match.status === 'success' &&
1005
- !this.getIsInvalid({ matchId: match.id, preload: opts?.preload }))
1057
+ !isMatchInvalid(match, {
1058
+ preload: opts?.preload,
1059
+ }))
1006
1060
  ) {
1007
1061
  return this.getRouteMatch(match.id)?.loadPromise
1008
1062
  }
@@ -1098,8 +1152,6 @@ export class Router<
1098
1152
  })
1099
1153
 
1100
1154
  await Promise.all(matchPromises)
1101
-
1102
- this.cleanMatches()
1103
1155
  }
1104
1156
 
1105
1157
  reload = () => {
@@ -1350,7 +1402,8 @@ export class Router<
1350
1402
  pick(d, [
1351
1403
  'fetchedAt',
1352
1404
  'invalid',
1353
- 'invalidAt',
1405
+ 'preloadMaxAge',
1406
+ 'maxAge',
1354
1407
  'id',
1355
1408
  'loaderData',
1356
1409
  'status',
@@ -1709,7 +1762,6 @@ export class Router<
1709
1762
  })
1710
1763
 
1711
1764
  this.resetNextScroll = location.resetScroll ?? true
1712
- console.log('resetScroll', this.resetNextScroll)
1713
1765
 
1714
1766
  return this.latestLoadPromise
1715
1767
  }
@@ -1752,29 +1804,24 @@ export class Router<
1752
1804
  const route = this.getRoute(match.routeId)
1753
1805
  const updatedAt = opts?.updatedAt ?? Date.now()
1754
1806
 
1755
- const preloadInvalidAt =
1756
- updatedAt +
1757
- (opts?.maxAge ??
1758
- route.options.preloadMaxAge ??
1759
- this.options.defaultPreloadMaxAge ??
1760
- 5000)
1807
+ const preloadMaxAge =
1808
+ opts?.maxAge ??
1809
+ route.options.preloadMaxAge ??
1810
+ this.options.defaultPreloadMaxAge ??
1811
+ 5000
1761
1812
 
1762
- const invalidAt =
1763
- updatedAt +
1764
- (opts?.maxAge ??
1765
- route.options.maxAge ??
1766
- this.options.defaultMaxAge ??
1767
- Infinity)
1813
+ const maxAge =
1814
+ opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1
1768
1815
 
1769
1816
  this.setRouteMatch(id, (s) => ({
1770
1817
  ...s,
1771
1818
  error: undefined,
1772
1819
  status: 'success',
1773
1820
  isFetching: false,
1774
- updatedAt: Date.now(),
1821
+ updatedAt: updatedAt,
1775
1822
  loaderData: functionalUpdate(updater, s.loaderData),
1776
- preloadInvalidAt,
1777
- invalidAt,
1823
+ preloadMaxAge,
1824
+ maxAge,
1778
1825
  }))
1779
1826
  }
1780
1827
 
@@ -1810,27 +1857,6 @@ export class Router<
1810
1857
  return this.reload()
1811
1858
  }
1812
1859
  }
1813
-
1814
- getIsInvalid = (opts?: { matchId: string; preload?: boolean }): boolean => {
1815
- if (!opts?.matchId) {
1816
- return !!this.state.matches.find((d) =>
1817
- this.getIsInvalid({ matchId: d.id, preload: opts?.preload }),
1818
- )
1819
- }
1820
-
1821
- const match = this.getRouteMatch(opts?.matchId)
1822
-
1823
- if (!match) {
1824
- return false
1825
- }
1826
-
1827
- const now = Date.now()
1828
-
1829
- return (
1830
- match.invalid ||
1831
- (opts?.preload ? match.preloadInvalidAt : match.invalidAt) < now
1832
- )
1833
- }
1834
1860
  }
1835
1861
 
1836
1862
  // Detect if we're in the DOM
@@ -1900,3 +1926,22 @@ export function lazyFn<
1900
1926
  return imported[key || 'default'](...args)
1901
1927
  }
1902
1928
  }
1929
+
1930
+ export function isMatchInvalid(
1931
+ match: AnyRouteMatch,
1932
+ opts?: { preload?: boolean },
1933
+ ) {
1934
+ const now = Date.now()
1935
+
1936
+ if (match.invalid) {
1937
+ return true
1938
+ }
1939
+
1940
+ if (opts?.preload) {
1941
+ return match.preloadMaxAge < 0
1942
+ ? false
1943
+ : match.updatedAt + match.preloadMaxAge < now
1944
+ }
1945
+
1946
+ return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now
1947
+ }