@tanstack/router-core 0.0.1-beta.162 → 0.0.1-beta.163

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.162",
4
+ "version": "0.0.1-beta.163",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -43,7 +43,7 @@
43
43
  "tiny-invariant": "^1.3.1",
44
44
  "tiny-warning": "^1.0.3",
45
45
  "@gisatcz/cross-package-react-context": "^0.2.0",
46
- "@tanstack/react-store": "0.0.1-beta.162"
46
+ "@tanstack/react-store": "0.0.1-beta.163"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "rollup --config rollup.config.js",
package/src/history.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  export interface RouterHistory {
6
6
  location: RouterLocation
7
- listen: (cb: () => void) => () => void
7
+ subscribe: (cb: () => void) => () => void
8
8
  push: (path: string, state?: any) => void
9
9
  replace: (path: string, state?: any) => void
10
10
  go: (index: number) => void
@@ -45,7 +45,7 @@ const stopBlocking = () => {
45
45
 
46
46
  function createHistory(opts: {
47
47
  getLocation: () => RouterLocation
48
- listener: false | ((onUpdate: () => void) => () => void)
48
+ subscriber: false | ((onUpdate: () => void) => () => void)
49
49
  pushState: (path: string, state: any) => void
50
50
  replaceState: (path: string, state: any) => void
51
51
  go: (n: number) => void
@@ -55,7 +55,7 @@ function createHistory(opts: {
55
55
  }): RouterHistory {
56
56
  let location = opts.getLocation()
57
57
  let unsub = () => {}
58
- let listeners = new Set<() => void>()
58
+ let subscribers = new Set<() => void>()
59
59
  let blockers: BlockerFn[] = []
60
60
  let queue: (() => void)[] = []
61
61
 
@@ -72,7 +72,7 @@ function createHistory(opts: {
72
72
  queue.shift()?.()
73
73
  }
74
74
 
75
- if (!opts.listener) {
75
+ if (!opts.subscriber) {
76
76
  onUpdate()
77
77
  }
78
78
  }
@@ -84,25 +84,25 @@ function createHistory(opts: {
84
84
 
85
85
  const onUpdate = () => {
86
86
  location = opts.getLocation()
87
- listeners.forEach((listener) => listener())
87
+ subscribers.forEach((subscriber) => subscriber())
88
88
  }
89
89
 
90
90
  return {
91
91
  get location() {
92
92
  return location
93
93
  },
94
- listen: (cb: () => void) => {
95
- if (listeners.size === 0) {
94
+ subscribe: (cb: () => void) => {
95
+ if (subscribers.size === 0) {
96
96
  unsub =
97
- typeof opts.listener === 'function'
98
- ? opts.listener(onUpdate)
97
+ typeof opts.subscriber === 'function'
98
+ ? opts.subscriber(onUpdate)
99
99
  : () => {}
100
100
  }
101
- listeners.add(cb)
101
+ subscribers.add(cb)
102
102
 
103
103
  return () => {
104
- listeners.delete(cb)
105
- if (listeners.size === 0) {
104
+ subscribers.delete(cb)
105
+ if (subscribers.size === 0) {
106
106
  unsub()
107
107
  }
108
108
  }
@@ -166,7 +166,7 @@ export function createBrowserHistory(opts?: {
166
166
 
167
167
  return createHistory({
168
168
  getLocation,
169
- listener: (onUpdate) => {
169
+ subscriber: (onUpdate) => {
170
170
  window.addEventListener(pushStateEvent, onUpdate)
171
171
  window.addEventListener(popStateEvent, onUpdate)
172
172
 
@@ -234,7 +234,7 @@ export function createMemoryHistory(
234
234
 
235
235
  return createHistory({
236
236
  getLocation,
237
- listener: false,
237
+ subscriber: false,
238
238
  pushState: (path, state) => {
239
239
  currentState = {
240
240
  ...state,
package/src/route.ts CHANGED
@@ -312,7 +312,7 @@ export type UpdatableRouteOptions<
312
312
  gcMaxAge?: number
313
313
  // This async function is called before a route is loaded.
314
314
  // If an error is thrown here, the route's loader will not be called.
315
- // If thrown during a navigation, the navigation will be cancelled and the error will be passed to the `onLoadError` function.
315
+ // If thrown during a navigation, the navigation will be cancelled and the error will be passed to the `onError` function.
316
316
  // If thrown during a preload event, the error will be logged to the console.
317
317
  beforeLoad?: (
318
318
  opts: LoaderContext<
@@ -323,15 +323,6 @@ export type UpdatableRouteOptions<
323
323
  TContext
324
324
  >,
325
325
  ) => Promise<void> | void
326
- // This function will be called if the route's loader throws an error **during an attempted navigation**.
327
- // If you want to redirect due to an error, call `router.navigate()` from within this function.
328
- onBeforeLoadError?: (err: any) => void
329
- // This function will be called if the route's validateSearch option throws an error **during an attempted validation**.
330
- // If you want to redirect due to an error, call `router.navigate()` from within this function.
331
- // If you want to display the errorComponent, rethrow the error
332
- onValidateSearchError?: (err: any) => void
333
- onParseParamsError?: (err: any) => void
334
- onLoadError?: (err: any) => void
335
326
  onError?: (err: any) => void
336
327
  // This function is called
337
328
  // when moving from an inactive state to an active one. Likewise, when moving from
package/src/router.ts CHANGED
@@ -171,7 +171,6 @@ export interface RouterOptions<
171
171
  routeTree?: TRouteTree
172
172
  basepath?: string
173
173
  createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
174
- onRouteChange?: () => void
175
174
  context?: TRouteTree['types']['routerContext']
176
175
  Wrap?: React.ComponentType<{
177
176
  children: React.ReactNode
@@ -197,7 +196,7 @@ export interface RouterState<
197
196
  lastUpdated: number
198
197
  }
199
198
 
200
- export type ListenerFn = () => void
199
+ export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
201
200
 
202
201
  export interface BuildNextOptions {
203
202
  to?: string | number | null
@@ -249,6 +248,28 @@ export const componentTypes = [
249
248
  'pendingComponent',
250
249
  ] as const
251
250
 
251
+ export type RouterEvents = {
252
+ onBeforeLoad: {
253
+ type: 'onBeforeLoad'
254
+ from: ParsedLocation
255
+ to: ParsedLocation
256
+ pathChanged: boolean
257
+ }
258
+ onLoad: {
259
+ type: 'onLoad'
260
+ from: ParsedLocation
261
+ to: ParsedLocation
262
+ pathChanged: boolean
263
+ }
264
+ }
265
+
266
+ export type RouterEvent = RouterEvents[keyof RouterEvents]
267
+
268
+ export type RouterListener<TRouterEvent extends RouterEvent> = {
269
+ eventType: TRouterEvent['type']
270
+ fn: ListenerFn<TRouterEvent>
271
+ }
272
+
252
273
  export class Router<
253
274
  TRouteTree extends AnyRoute = AnyRoute,
254
275
  TDehydrated extends Record<string, any> = Record<string, any>,
@@ -343,6 +364,32 @@ export class Router<
343
364
  }
344
365
  }
345
366
 
367
+ subscribers = new Set<RouterListener<RouterEvent>>()
368
+
369
+ subscribe = <TType extends keyof RouterEvents>(
370
+ eventType: TType,
371
+ fn: ListenerFn<RouterEvents[TType]>,
372
+ ) => {
373
+ const listener: RouterListener<any> = {
374
+ eventType,
375
+ fn,
376
+ }
377
+
378
+ this.subscribers.add(listener)
379
+
380
+ return () => {
381
+ this.subscribers.delete(listener)
382
+ }
383
+ }
384
+
385
+ #emit = (routerEvent: RouterEvent) => {
386
+ this.subscribers.forEach((listener) => {
387
+ if (listener.eventType === routerEvent.type) {
388
+ listener.fn(routerEvent)
389
+ }
390
+ })
391
+ }
392
+
346
393
  reset = () => {
347
394
  this.__store.setState((s) => Object.assign(s, getInitialRouterState()))
348
395
  }
@@ -384,7 +431,7 @@ export class Router<
384
431
  location: parsedLocation as any,
385
432
  }))
386
433
 
387
- this.#unsubHistory = this.history.listen(() => {
434
+ this.#unsubHistory = this.history.subscribe(() => {
388
435
  this.safeLoad({
389
436
  next: this.#parseLocation(this.state.location),
390
437
  })
@@ -435,6 +482,11 @@ export class Router<
435
482
 
436
483
  load = async (opts?: { next?: ParsedLocation; throwOnError?: boolean }) => {
437
484
  const promise = new Promise<void>(async (resolve, reject) => {
485
+ const prevLocation = this.state.resolvedLocation
486
+ const pathDidChange = !!(
487
+ opts?.next && prevLocation!.href !== opts.next.href
488
+ )
489
+
438
490
  let latestPromise: Promise<void> | undefined | null
439
491
 
440
492
  const checkLatest = (): undefined | Promise<void> | null => {
@@ -448,6 +500,13 @@ export class Router<
448
500
 
449
501
  let pendingMatches!: RouteMatch<any, any>[]
450
502
 
503
+ this.#emit({
504
+ type: 'onBeforeLoad',
505
+ from: prevLocation,
506
+ to: opts?.next ?? this.state.location,
507
+ pathChanged: pathDidChange,
508
+ })
509
+
451
510
  this.__store.batch(() => {
452
511
  if (opts?.next) {
453
512
  // Ingest the new location
@@ -489,8 +548,6 @@ export class Router<
489
548
  return latestPromise
490
549
  }
491
550
 
492
- const prevLocation = this.state.resolvedLocation
493
-
494
551
  this.__store.setState((s) => ({
495
552
  ...s,
496
553
  status: 'idle',
@@ -499,9 +556,12 @@ export class Router<
499
556
  pendingMatchIds: [],
500
557
  }))
501
558
 
502
- if (prevLocation!.href !== this.state.location.href) {
503
- this.options.onRouteChange?.()
504
- }
559
+ this.#emit({
560
+ type: 'onLoad',
561
+ from: prevLocation,
562
+ to: this.state.location,
563
+ pathChanged: pathDidChange,
564
+ })
505
565
 
506
566
  resolve()
507
567
  } catch (err) {
@@ -844,19 +904,16 @@ export class Router<
844
904
  for (const [index, match] of resolvedMatches.entries()) {
845
905
  const route = this.getRoute(match.routeId)
846
906
 
847
- const handleError = (
848
- err: any,
849
- handler: undefined | ((err: any) => void),
850
- ) => {
907
+ const handleError = (err: any, code: string) => {
908
+ err.routerCode = code
851
909
  firstBadMatchIndex = firstBadMatchIndex ?? index
852
- handler = handler || route.options.onError
853
910
 
854
911
  if (isRedirect(err)) {
855
912
  throw err
856
913
  }
857
914
 
858
915
  try {
859
- handler?.(err)
916
+ route.options.onError?.(err)
860
917
  } catch (errorHandlerErr) {
861
918
  err = errorHandlerErr
862
919
 
@@ -874,11 +931,11 @@ export class Router<
874
931
  }
875
932
 
876
933
  if (match.paramsError) {
877
- handleError(match.paramsError, route.options.onParseParamsError)
934
+ handleError(match.paramsError, 'PARSE_PARAMS')
878
935
  }
879
936
 
880
937
  if (match.searchError) {
881
- handleError(match.searchError, route.options.onValidateSearchError)
938
+ handleError(match.searchError, 'VALIDATE_SEARCH')
882
939
  }
883
940
 
884
941
  let didError = false
@@ -889,7 +946,7 @@ export class Router<
889
946
  preload: !!opts?.preload,
890
947
  })
891
948
  } catch (err) {
892
- handleError(err, route.options.onBeforeLoadError)
949
+ handleError(err, 'BEFORE_LOAD')
893
950
  didError = true
894
951
  }
895
952
 
@@ -968,34 +1025,20 @@ export class Router<
968
1025
  if ((latestPromise = checkLatest())) return await latestPromise
969
1026
 
970
1027
  this.setRouteMatchData(match.id, () => loader, opts)
971
- } catch (loaderError) {
972
- let latestError = loaderError
1028
+ } catch (error) {
973
1029
  if ((latestPromise = checkLatest())) return await latestPromise
974
- if (handleIfRedirect(loaderError)) return
975
-
976
- if (route.options.onLoadError) {
977
- try {
978
- route.options.onLoadError(loaderError)
979
- } catch (onLoadError) {
980
- latestError = onLoadError
981
- if (handleIfRedirect(onLoadError)) return
982
- }
983
- }
1030
+ if (handleIfRedirect(error)) return
984
1031
 
985
- if (
986
- (!route.options.onLoadError || latestError !== loaderError) &&
987
- route.options.onError
988
- ) {
989
- try {
990
- route.options.onError(latestError)
991
- } catch (onErrorError) {
992
- if (handleIfRedirect(onErrorError)) return
993
- }
1032
+ try {
1033
+ route.options.onError?.(error)
1034
+ } catch (onErrorError) {
1035
+ error = onErrorError
1036
+ if (handleIfRedirect(onErrorError)) return
994
1037
  }
995
1038
 
996
1039
  this.setRouteMatch(match.id, (s) => ({
997
1040
  ...s,
998
- error: loaderError,
1041
+ error,
999
1042
  status: 'error',
1000
1043
  isFetching: false,
1001
1044
  updatedAt: Date.now(),