@tanstack/react-router 0.0.1-beta.209 → 0.0.1-beta.210

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.
Files changed (72) hide show
  1. package/build/cjs/CatchBoundary.js +125 -0
  2. package/build/cjs/CatchBoundary.js.map +1 -0
  3. package/build/cjs/Matches.js +223 -0
  4. package/build/cjs/Matches.js.map +1 -0
  5. package/build/cjs/RouterProvider.js +99 -53
  6. package/build/cjs/RouterProvider.js.map +1 -1
  7. package/build/cjs/index.js +46 -37
  8. package/build/cjs/index.js.map +1 -1
  9. package/build/cjs/lazyRouteComponent.js +57 -0
  10. package/build/cjs/lazyRouteComponent.js.map +1 -0
  11. package/build/cjs/link.js +150 -0
  12. package/build/cjs/link.js.map +1 -0
  13. package/build/cjs/route.js +9 -5
  14. package/build/cjs/route.js.map +1 -1
  15. package/build/cjs/router.js.map +1 -1
  16. package/build/cjs/searchParams.js.map +1 -1
  17. package/build/cjs/useBlocker.js +64 -0
  18. package/build/cjs/useBlocker.js.map +1 -0
  19. package/build/cjs/useNavigate.js +78 -0
  20. package/build/cjs/useNavigate.js.map +1 -0
  21. package/build/cjs/useParams.js +28 -0
  22. package/build/cjs/useParams.js.map +1 -0
  23. package/build/cjs/useSearch.js +27 -0
  24. package/build/cjs/useSearch.js.map +1 -0
  25. package/build/cjs/utils.js +30 -1
  26. package/build/cjs/utils.js.map +1 -1
  27. package/build/esm/index.js +491 -514
  28. package/build/esm/index.js.map +1 -1
  29. package/build/stats-html.html +1 -1
  30. package/build/stats-react.json +484 -208
  31. package/build/types/CatchBoundary.d.ts +33 -0
  32. package/build/types/Matches.d.ts +31 -0
  33. package/build/types/RouterProvider.d.ts +42 -18
  34. package/build/types/fileRoute.d.ts +7 -7
  35. package/build/types/index.d.ts +13 -7
  36. package/build/types/injectHtml.d.ts +0 -0
  37. package/build/types/lazyRouteComponent.d.ts +2 -0
  38. package/build/types/link.d.ts +10 -3
  39. package/build/types/route.d.ts +39 -7
  40. package/build/types/router.d.ts +6 -7
  41. package/build/types/useBlocker.d.ts +8 -0
  42. package/build/types/useNavigate.d.ts +20 -0
  43. package/build/types/useParams.d.ts +7 -0
  44. package/build/types/useSearch.d.ts +7 -0
  45. package/build/types/utils.d.ts +17 -0
  46. package/build/umd/index.development.js +492 -513
  47. package/build/umd/index.development.js.map +1 -1
  48. package/build/umd/index.production.js +1 -1
  49. package/build/umd/index.production.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/CatchBoundary.tsx +97 -0
  52. package/src/Matches.tsx +315 -0
  53. package/src/RouterProvider.tsx +317 -251
  54. package/src/index.tsx +17 -8
  55. package/src/injectHtml.ts +28 -0
  56. package/src/lazyRouteComponent.tsx +33 -0
  57. package/src/{link.ts → link.tsx} +163 -3
  58. package/src/location.ts +1 -0
  59. package/src/route.ts +86 -16
  60. package/src/router.ts +6 -7
  61. package/src/searchParams.ts +1 -0
  62. package/src/useBlocker.tsx +34 -0
  63. package/src/useNavigate.tsx +109 -0
  64. package/src/useParams.tsx +25 -0
  65. package/src/useSearch.tsx +25 -0
  66. package/src/utils.ts +83 -3
  67. package/build/cjs/react.js +0 -620
  68. package/build/cjs/react.js.map +0 -1
  69. package/build/types/RouteMatch.d.ts +0 -23
  70. package/build/types/react.d.ts +0 -141
  71. package/src/RouteMatch.ts +0 -28
  72. package/src/react.tsx +0 -1013
@@ -1,27 +1,21 @@
1
+ import {
2
+ HistoryLocation,
3
+ HistoryState,
4
+ RouterHistory,
5
+ createBrowserHistory,
6
+ } from '@tanstack/history'
1
7
  import * as React from 'react'
2
- import { AnyPathParams, AnySearchSchema, Route } from './route'
8
+ import invariant from 'tiny-invariant'
9
+ import warning from 'tiny-warning'
10
+ import { Matches } from './Matches'
3
11
  import {
4
- RegisteredRouter,
5
- DehydratedRouteMatch,
6
- componentTypes,
7
- BuildNextOptions,
8
- RouterOptions,
9
- } from './router'
12
+ LinkInfo,
13
+ LinkOptions,
14
+ NavigateOptions,
15
+ ResolveRelativePath,
16
+ ToOptions,
17
+ } from './link'
10
18
  import { ParsedLocation } from './location'
11
- import { AnyRouteMatch } from './RouteMatch'
12
- import { RouteMatch } from './RouteMatch'
13
- import { isRedirect } from './redirects'
14
- import {
15
- functionalUpdate,
16
- replaceEqualDeep,
17
- useStableCallback,
18
- last,
19
- pick,
20
- partialDeepEqual,
21
- NoInfer,
22
- PickAsRequired,
23
- } from './utils'
24
- import { RouterProps, Matches } from './react'
25
19
  import {
26
20
  cleanPath,
27
21
  interpolatePath,
@@ -32,33 +26,42 @@ import {
32
26
  trimPath,
33
27
  trimPathRight,
34
28
  } from './path'
35
- import invariant from 'tiny-invariant'
29
+ import { isRedirect } from './redirects'
30
+ import { AnyPathParams, AnyRoute, AnySearchSchema, Route } from './route'
36
31
  import {
37
32
  FullSearchSchema,
33
+ ParseRoute,
38
34
  RouteById,
35
+ RouteIds,
39
36
  RoutePaths,
40
37
  RoutesById,
41
38
  RoutesByPath,
42
39
  } from './routeInfo'
43
40
  import {
44
- LinkInfo,
45
- LinkOptions,
46
- NavigateOptions,
47
- ResolveRelativePath,
48
- ToOptions,
49
- } from './link'
41
+ BuildNextOptions,
42
+ DehydratedRouteMatch,
43
+ RegisteredRouter,
44
+ Router,
45
+ RouterOptions,
46
+ RouterState,
47
+ componentTypes,
48
+ } from './router'
50
49
  import {
51
- HistoryLocation,
52
- HistoryState,
53
- RouterHistory,
54
- createBrowserHistory,
55
- } from '.'
56
- import { AnyRoute } from './route'
57
- import { RouterState } from './router'
50
+ NoInfer,
51
+ PickAsRequired,
52
+ functionalUpdate,
53
+ last,
54
+ partialDeepEqual,
55
+ pick,
56
+ replaceEqualDeep,
57
+ useStableCallback,
58
+ } from './utils'
59
+ import { MatchRouteOptions } from './Matches'
58
60
 
59
61
  export interface CommitLocationOptions {
60
62
  replace?: boolean
61
63
  resetScroll?: boolean
64
+ startTransition?: boolean
62
65
  }
63
66
 
64
67
  export interface MatchLocation {
@@ -68,13 +71,6 @@ export interface MatchLocation {
68
71
  from?: string
69
72
  }
70
73
 
71
- export interface MatchRouteOptions {
72
- pending?: boolean
73
- caseSensitive?: boolean
74
- includeSearch?: boolean
75
- fuzzy?: boolean
76
- }
77
-
78
74
  type LinkCurrentTargetElement = {
79
75
  preloadTimeout?: null | ReturnType<typeof setTimeout>
80
76
  }
@@ -83,7 +79,6 @@ export type BuildLinkFn<TRouteTree extends AnyRoute> = <
83
79
  TFrom extends RoutePaths<TRouteTree> = '/',
84
80
  TTo extends string = '',
85
81
  >(
86
- state: RouterState,
87
82
  dest: LinkOptions<TRouteTree, TFrom, TTo>,
88
83
  ) => LinkInfo
89
84
 
@@ -104,7 +99,6 @@ export type MatchRouteFn<TRouteTree extends AnyRoute> = <
104
99
  TTo extends string = '',
105
100
  TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
106
101
  >(
107
- state: RouterState<TRouteTree>,
108
102
  location: ToOptions<TRouteTree, TFrom, TTo>,
109
103
  opts?: MatchRouteOptions,
110
104
  ) => false | RouteById<TRouteTree, TResolved>['types']['allParams']
@@ -115,7 +109,9 @@ export type LoadFn = (opts?: {
115
109
  __dehydratedMatches?: DehydratedRouteMatch[]
116
110
  }) => Promise<void>
117
111
 
118
- const preloadWarning = 'Error preloading route! ☝️'
112
+ export type BuildLocationFn<TRouteTree extends AnyRoute> = (
113
+ opts: BuildNextOptions,
114
+ ) => ParsedLocation
119
115
 
120
116
  export type RouterContext<
121
117
  TRouteTree extends AnyRoute,
@@ -130,6 +126,7 @@ export type RouterContext<
130
126
  options: RouterOptions<TRouteTree>
131
127
  history: RouterHistory
132
128
  load: LoadFn
129
+ buildLocation: BuildLocationFn<TRouteTree>
133
130
  }
134
131
 
135
132
  export const routerContext = React.createContext<RouterContext<any>>(null!)
@@ -138,13 +135,22 @@ if (typeof document !== 'undefined') {
138
135
  window.__TSR_ROUTER_CONTEXT__ = routerContext as any
139
136
  }
140
137
 
138
+ const preloadWarning = 'Error preloading route! ☝️'
139
+
140
+ function isCtrlEvent(e: MouseEvent) {
141
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
142
+ }
143
+
144
+ export class SearchParamError extends Error {}
145
+
146
+ export class PathParamError extends Error {}
147
+
141
148
  export function getInitialRouterState(
142
149
  location: ParsedLocation,
143
150
  ): RouterState<any> {
144
151
  return {
145
152
  status: 'idle',
146
- isFetching: false,
147
- resolvedLocation: location!,
153
+ resolvedLocation: undefined,
148
154
  location: location!,
149
155
  matches: [],
150
156
  pendingMatches: [],
@@ -239,9 +245,27 @@ export function RouterProvider<
239
245
  },
240
246
  )
241
247
 
242
- const [state, setState] = React.useState<RouterState<TRouteTree>>(() =>
248
+ const [preState, setState] = React.useState<RouterState<TRouteTree>>(() =>
243
249
  getInitialRouterState(parseLocation()),
244
250
  )
251
+ const [isTransitioning, startReactTransition] = React.useTransition()
252
+
253
+ const state = React.useMemo<RouterState<TRouteTree>>(
254
+ () => ({
255
+ ...preState,
256
+ status: isTransitioning ? 'pending' : 'idle',
257
+ }),
258
+ [preState, isTransitioning],
259
+ )
260
+
261
+ React.useLayoutEffect(() => {
262
+ if (!isTransitioning && state.resolvedLocation !== state.location) {
263
+ setState((s) => ({
264
+ ...s,
265
+ resolvedLocation: s.location,
266
+ }))
267
+ }
268
+ })
245
269
 
246
270
  const basepath = `/${trimPath(options.basepath ?? '') ?? ''}`
247
271
 
@@ -528,10 +552,8 @@ export function RouterProvider<
528
552
  },
529
553
  )
530
554
 
531
- const buildLocation = useStableCallback(
532
- <TRouteTree extends AnyRoute>(
533
- opts: BuildNextOptions = {},
534
- ): ParsedLocation => {
555
+ const buildLocation = useStableCallback<BuildLocationFn<TRouteTree>>(
556
+ (opts) => {
535
557
  const build = (
536
558
  dest: BuildNextOptions & {
537
559
  unmaskOnReload?: boolean
@@ -704,7 +726,10 @@ export function RouterProvider<
704
726
  )
705
727
 
706
728
  const commitLocation = useStableCallback(
707
- async (next: ParsedLocation & CommitLocationOptions) => {
729
+ async ({
730
+ startTransition,
731
+ ...next
732
+ }: ParsedLocation & CommitLocationOptions) => {
708
733
  if (navigateTimeoutRef.current) clearTimeout(navigateTimeoutRef.current)
709
734
 
710
735
  const isSameUrl = latestLocationRef.current.href === next.href
@@ -738,10 +763,18 @@ export function RouterProvider<
738
763
  }
739
764
  }
740
765
 
741
- history[next.replace ? 'replace' : 'push'](
742
- nextHistory.href,
743
- nextHistory.state,
744
- )
766
+ const apply = () => {
767
+ history[next.replace ? 'replace' : 'push'](
768
+ nextHistory.href,
769
+ nextHistory.state,
770
+ )
771
+ }
772
+
773
+ if (startTransition ?? true) {
774
+ startReactTransition(apply)
775
+ } else {
776
+ apply()
777
+ }
745
778
  }
746
779
 
747
780
  resetNextScrollRef.current = next.resetScroll ?? true
@@ -754,11 +787,13 @@ export function RouterProvider<
754
787
  ({
755
788
  replace,
756
789
  resetScroll,
790
+ startTransition,
757
791
  ...rest
758
792
  }: BuildNextOptions & CommitLocationOptions = {}) => {
759
793
  const location = buildLocation(rest)
760
794
  return commitLocation({
761
795
  ...location,
796
+ startTransition,
762
797
  replace,
763
798
  resetScroll,
764
799
  })
@@ -858,7 +893,9 @@ export function RouterProvider<
858
893
  preload: !!preload,
859
894
  context: parentContext,
860
895
  location: state.location, // TODO: This might need to be latestLocationRef.current...?
861
- navigate: (opts) => navigate({ ...opts, from: match.pathname }),
896
+ navigate: (opts) =>
897
+ navigate({ ...opts, from: match.pathname } as any),
898
+ buildLocation,
862
899
  })) ?? ({} as any)
863
900
 
864
901
  const context = {
@@ -931,7 +968,10 @@ export function RouterProvider<
931
968
  navigate({ ...opts, from: match.pathname }),
932
969
  })
933
970
 
934
- await Promise.all([componentsPromise, loaderPromise])
971
+ const [_, loaderContext] = await Promise.all([
972
+ componentsPromise,
973
+ loaderPromise,
974
+ ])
935
975
  if ((latestPromise = checkLatest())) return await latestPromise
936
976
 
937
977
  matches[index] = match = {
@@ -1000,7 +1040,7 @@ export function RouterProvider<
1000
1040
  const load = useStableCallback<LoadFn>(async () => {
1001
1041
  const promise = new Promise<void>(async (resolve, reject) => {
1002
1042
  const next = latestLocationRef.current
1003
- const prevLocation = state.resolvedLocation
1043
+ const prevLocation = state.resolvedLocation || state.location
1004
1044
  const pathDidChange = !!(next && prevLocation!.href !== next.href)
1005
1045
  let latestPromise: Promise<void> | undefined | null
1006
1046
 
@@ -1014,12 +1054,6 @@ export function RouterProvider<
1014
1054
  pathChanged: pathDidChange,
1015
1055
  })
1016
1056
 
1017
- // Ingest the new location
1018
- setState((s) => ({
1019
- ...s,
1020
- location: next,
1021
- }))
1022
-
1023
1057
  // Match the routes
1024
1058
  let matches: RouteMatch<any, any>[] = matchRoutes(
1025
1059
  next.pathname,
@@ -1029,9 +1063,9 @@ export function RouterProvider<
1029
1063
  },
1030
1064
  )
1031
1065
 
1066
+ // Ingest the new matches
1032
1067
  setState((s) => ({
1033
1068
  ...s,
1034
- status: 'pending',
1035
1069
  matches,
1036
1070
  }))
1037
1071
 
@@ -1063,11 +1097,11 @@ export function RouterProvider<
1063
1097
  // state.pendingMatches.includes(id),
1064
1098
  // )
1065
1099
 
1066
- setState((s) => ({
1067
- ...s,
1068
- status: 'idle',
1069
- resolvedLocation: s.location,
1070
- }))
1100
+ // setState((s) => ({
1101
+ // ...s,
1102
+ // status: 'idle',
1103
+ // resolvedLocation: s.location,
1104
+ // }))
1071
1105
 
1072
1106
  // TODO:
1073
1107
  // ;(
@@ -1123,134 +1157,132 @@ export function RouterProvider<
1123
1157
  },
1124
1158
  )
1125
1159
 
1126
- const buildLink = useStableCallback<BuildLinkFn<TRouteTree>>(
1127
- (state, dest) => {
1128
- // If this link simply reloads the current route,
1129
- // make sure it has a new key so it will trigger a data refresh
1130
-
1131
- // If this `to` is a valid external URL, return
1132
- // null for LinkUtils
1133
-
1134
- const {
1135
- to,
1136
- preload: userPreload,
1137
- preloadDelay: userPreloadDelay,
1138
- activeOptions,
1139
- disabled,
1140
- target,
1141
- replace,
1142
- resetScroll,
1143
- } = dest
1160
+ const buildLink = useStableCallback<BuildLinkFn<TRouteTree>>((dest) => {
1161
+ // If this link simply reloads the current route,
1162
+ // make sure it has a new key so it will trigger a data refresh
1144
1163
 
1145
- try {
1146
- new URL(`${to}`)
1147
- return {
1148
- type: 'external',
1149
- href: to as any,
1150
- }
1151
- } catch (e) {}
1164
+ // If this `to` is a valid external URL, return
1165
+ // null for LinkUtils
1152
1166
 
1153
- const nextOpts = dest
1167
+ const {
1168
+ to,
1169
+ preload: userPreload,
1170
+ preloadDelay: userPreloadDelay,
1171
+ activeOptions,
1172
+ disabled,
1173
+ target,
1174
+ replace,
1175
+ resetScroll,
1176
+ startTransition,
1177
+ } = dest
1154
1178
 
1155
- const next = buildLocation(nextOpts as any)
1179
+ try {
1180
+ new URL(`${to}`)
1181
+ return {
1182
+ type: 'external',
1183
+ href: to as any,
1184
+ }
1185
+ } catch (e) {}
1186
+
1187
+ const nextOpts = dest
1188
+ const next = buildLocation(nextOpts as any)
1189
+
1190
+ const preload = userPreload ?? options.defaultPreload
1191
+ const preloadDelay = userPreloadDelay ?? options.defaultPreloadDelay ?? 0
1192
+
1193
+ // Compare path/hash for matches
1194
+ const currentPathSplit = latestLocationRef.current.pathname.split('/')
1195
+ const nextPathSplit = next.pathname.split('/')
1196
+ const pathIsFuzzyEqual = nextPathSplit.every(
1197
+ (d, i) => d === currentPathSplit[i],
1198
+ )
1199
+ // Combine the matches based on user options
1200
+ const pathTest = activeOptions?.exact
1201
+ ? latestLocationRef.current.pathname === next.pathname
1202
+ : pathIsFuzzyEqual
1203
+ const hashTest = activeOptions?.includeHash
1204
+ ? latestLocationRef.current.hash === next.hash
1205
+ : true
1206
+ const searchTest =
1207
+ activeOptions?.includeSearch ?? true
1208
+ ? partialDeepEqual(latestLocationRef.current.search, next.search)
1209
+ : true
1156
1210
 
1157
- const preload = userPreload ?? options.defaultPreload
1158
- const preloadDelay = userPreloadDelay ?? options.defaultPreloadDelay ?? 0
1211
+ // The final "active" test
1212
+ const isActive = pathTest && hashTest && searchTest
1159
1213
 
1160
- // Compare path/hash for matches
1161
- const currentPathSplit = latestLocationRef.current.pathname.split('/')
1162
- const nextPathSplit = next.pathname.split('/')
1163
- const pathIsFuzzyEqual = nextPathSplit.every(
1164
- (d, i) => d === currentPathSplit[i],
1165
- )
1166
- // Combine the matches based on user options
1167
- const pathTest = activeOptions?.exact
1168
- ? latestLocationRef.current.pathname === next.pathname
1169
- : pathIsFuzzyEqual
1170
- const hashTest = activeOptions?.includeHash
1171
- ? latestLocationRef.current.hash === next.hash
1172
- : true
1173
- const searchTest =
1174
- activeOptions?.includeSearch ?? true
1175
- ? partialDeepEqual(latestLocationRef.current.search, next.search)
1176
- : true
1177
-
1178
- // The final "active" test
1179
- const isActive = pathTest && hashTest && searchTest
1180
-
1181
- // The click handler
1182
- const handleClick = (e: MouseEvent) => {
1183
- if (
1184
- !disabled &&
1185
- !isCtrlEvent(e) &&
1186
- !e.defaultPrevented &&
1187
- (!target || target === '_self') &&
1188
- e.button === 0
1189
- ) {
1190
- e.preventDefault()
1191
-
1192
- // All is well? Navigate!
1193
- commitLocation({ ...next, replace, resetScroll })
1194
- }
1195
- }
1214
+ // The click handler
1215
+ const handleClick = (e: MouseEvent) => {
1216
+ if (
1217
+ !disabled &&
1218
+ !isCtrlEvent(e) &&
1219
+ !e.defaultPrevented &&
1220
+ (!target || target === '_self') &&
1221
+ e.button === 0
1222
+ ) {
1223
+ e.preventDefault()
1196
1224
 
1197
- // The click handler
1198
- const handleFocus = (e: MouseEvent) => {
1199
- if (preload) {
1200
- preloadRoute(nextOpts as any).catch((err) => {
1201
- console.warn(err)
1202
- console.warn(preloadWarning)
1203
- })
1204
- }
1225
+ // All is well? Navigate!
1226
+ commitLocation({ ...next, replace, resetScroll, startTransition })
1205
1227
  }
1228
+ }
1206
1229
 
1207
- const handleTouchStart = (e: TouchEvent) => {
1230
+ // The click handler
1231
+ const handleFocus = (e: MouseEvent) => {
1232
+ if (preload) {
1208
1233
  preloadRoute(nextOpts as any).catch((err) => {
1209
1234
  console.warn(err)
1210
1235
  console.warn(preloadWarning)
1211
1236
  })
1212
1237
  }
1238
+ }
1213
1239
 
1214
- const handleEnter = (e: MouseEvent) => {
1215
- const target = (e.target || {}) as LinkCurrentTargetElement
1240
+ const handleTouchStart = (e: TouchEvent) => {
1241
+ preloadRoute(nextOpts as any).catch((err) => {
1242
+ console.warn(err)
1243
+ console.warn(preloadWarning)
1244
+ })
1245
+ }
1216
1246
 
1217
- if (preload) {
1218
- if (target.preloadTimeout) {
1219
- return
1220
- }
1247
+ const handleEnter = (e: MouseEvent) => {
1248
+ const target = (e.target || {}) as LinkCurrentTargetElement
1221
1249
 
1222
- target.preloadTimeout = setTimeout(() => {
1223
- target.preloadTimeout = null
1224
- preloadRoute(nextOpts as any).catch((err) => {
1225
- console.warn(err)
1226
- console.warn(preloadWarning)
1227
- })
1228
- }, preloadDelay)
1250
+ if (preload) {
1251
+ if (target.preloadTimeout) {
1252
+ return
1229
1253
  }
1230
- }
1231
1254
 
1232
- const handleLeave = (e: MouseEvent) => {
1233
- const target = (e.target || {}) as LinkCurrentTargetElement
1234
-
1235
- if (target.preloadTimeout) {
1236
- clearTimeout(target.preloadTimeout)
1255
+ target.preloadTimeout = setTimeout(() => {
1237
1256
  target.preloadTimeout = null
1238
- }
1257
+ preloadRoute(nextOpts as any).catch((err) => {
1258
+ console.warn(err)
1259
+ console.warn(preloadWarning)
1260
+ })
1261
+ }, preloadDelay)
1239
1262
  }
1263
+ }
1240
1264
 
1241
- return {
1242
- type: 'internal',
1243
- next,
1244
- handleFocus,
1245
- handleClick,
1246
- handleEnter,
1247
- handleLeave,
1248
- handleTouchStart,
1249
- isActive,
1250
- disabled,
1265
+ const handleLeave = (e: MouseEvent) => {
1266
+ const target = (e.target || {}) as LinkCurrentTargetElement
1267
+
1268
+ if (target.preloadTimeout) {
1269
+ clearTimeout(target.preloadTimeout)
1270
+ target.preloadTimeout = null
1251
1271
  }
1252
- },
1253
- )
1272
+ }
1273
+
1274
+ return {
1275
+ type: 'internal',
1276
+ next,
1277
+ handleFocus,
1278
+ handleClick,
1279
+ handleEnter,
1280
+ handleLeave,
1281
+ handleTouchStart,
1282
+ isActive,
1283
+ disabled,
1284
+ }
1285
+ })
1254
1286
 
1255
1287
  const latestLocationRef = React.useRef(state.location)
1256
1288
 
@@ -1258,15 +1290,17 @@ export function RouterProvider<
1258
1290
  const unsub = history.subscribe(() => {
1259
1291
  latestLocationRef.current = parseLocation(latestLocationRef.current)
1260
1292
 
1261
- React.startTransition(() => {
1262
- if (state.location !== latestLocationRef.current) {
1263
- try {
1264
- load()
1265
- } catch (err) {
1266
- console.error(err)
1267
- }
1293
+ setState((s) => ({
1294
+ ...s,
1295
+ status: 'pending',
1296
+ }))
1297
+ if (state.location !== latestLocationRef.current) {
1298
+ try {
1299
+ load()
1300
+ } catch (err) {
1301
+ console.error(err)
1268
1302
  }
1269
- })
1303
+ }
1270
1304
  })
1271
1305
 
1272
1306
  const nextLocation = buildLocation({
@@ -1289,54 +1323,58 @@ export function RouterProvider<
1289
1323
 
1290
1324
  if (initialLoad.current) {
1291
1325
  initialLoad.current = false
1292
- try {
1293
- load()
1294
- } catch (err) {
1295
- console.error(err)
1296
- }
1326
+ startReactTransition(() => {
1327
+ try {
1328
+ load()
1329
+ } catch (err) {
1330
+ console.error(err)
1331
+ }
1332
+ })
1297
1333
  }
1298
1334
 
1299
- const isFetching = React.useMemo(
1300
- () => [...state.matches, ...state.pendingMatches].some((d) => d.isFetching),
1301
- [state.matches, state.pendingMatches],
1302
- )
1335
+ const matchRoute = useStableCallback<MatchRouteFn<TRouteTree>>(
1336
+ (location, opts) => {
1337
+ location = {
1338
+ ...location,
1339
+ to: location.to
1340
+ ? resolvePathWithBase((location.from || '') as string, location.to)
1341
+ : undefined,
1342
+ } as any
1303
1343
 
1304
- const matchRoute = useStableCallback((state, location, opts) => {
1305
- location = {
1306
- ...location,
1307
- to: location.to
1308
- ? resolvePathWithBase((location.from || '') as string, location.to)
1309
- : undefined,
1310
- } as any
1311
-
1312
- const next = buildLocation(location as any)
1313
- if (opts?.pending && state.status !== 'pending') {
1314
- return false
1315
- }
1344
+ const next = buildLocation(location as any)
1345
+
1346
+ if (opts?.pending && state.status !== 'pending') {
1347
+ return false
1348
+ }
1316
1349
 
1317
- const baseLocation = opts?.pending
1318
- ? latestLocationRef.current
1319
- : state.resolvedLocation
1350
+ const baseLocation = opts?.pending
1351
+ ? latestLocationRef.current
1352
+ : state.resolvedLocation
1320
1353
 
1321
- if (!baseLocation) {
1322
- return false
1323
- }
1354
+ // const baseLocation = state.resolvedLocation
1324
1355
 
1325
- const match = matchPathname(basepath, baseLocation.pathname, {
1326
- ...opts,
1327
- to: next.pathname,
1328
- }) as any
1356
+ if (!baseLocation) {
1357
+ return false
1358
+ }
1329
1359
 
1330
- if (!match) {
1331
- return false
1332
- }
1360
+ const match = matchPathname(basepath, baseLocation.pathname, {
1361
+ ...opts,
1362
+ to: next.pathname,
1363
+ }) as any
1333
1364
 
1334
- if (opts?.includeSearch ?? true) {
1335
- return partialDeepEqual(baseLocation.search, next.search) ? match : false
1336
- }
1365
+ if (!match) {
1366
+ return false
1367
+ }
1337
1368
 
1338
- return match
1339
- })
1369
+ if (match && (opts?.includeSearch ?? true)) {
1370
+ return partialDeepEqual(baseLocation.search, next.search)
1371
+ ? match
1372
+ : false
1373
+ }
1374
+
1375
+ return match
1376
+ },
1377
+ )
1340
1378
 
1341
1379
  const routerContextValue: RouterContext<TRouteTree> = {
1342
1380
  routeTree: router.routeTree,
@@ -1348,6 +1386,7 @@ export function RouterProvider<
1348
1386
  options,
1349
1387
  history,
1350
1388
  load,
1389
+ buildLocation,
1351
1390
  }
1352
1391
 
1353
1392
  return (
@@ -1357,35 +1396,62 @@ export function RouterProvider<
1357
1396
  )
1358
1397
  }
1359
1398
 
1360
- function mergeMatches<TRouteTree extends AnyRoute>(
1361
- prevMatchesById: Record<string, RouteMatch<TRouteTree>>,
1362
- nextMatches: AnyRouteMatch[],
1363
- ): Record<string, RouteMatch<TRouteTree>> {
1364
- let matchesById = { ...prevMatchesById }
1365
-
1366
- nextMatches.forEach((match) => {
1367
- if (!matchesById[match.id]) {
1368
- matchesById[match.id] = match
1369
- }
1370
-
1371
- matchesById[match.id] = {
1372
- ...matchesById[match.id],
1373
- ...match,
1374
- }
1375
- })
1376
-
1377
- return matchesById
1378
- }
1379
-
1380
- function isCtrlEvent(e: MouseEvent) {
1381
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
1382
- }
1383
- export class SearchParamError extends Error {}
1384
- export class PathParamError extends Error {}
1385
-
1386
1399
  export function getRouteMatch<TRouteTree extends AnyRoute>(
1387
1400
  state: RouterState<TRouteTree>,
1388
1401
  id: string,
1389
1402
  ): undefined | RouteMatch<TRouteTree> {
1390
1403
  return [...state.pendingMatches, ...state.matches].find((d) => d.id === id)
1391
1404
  }
1405
+
1406
+ export function useRouterState<
1407
+ TSelected = RouterState<RegisteredRouter['routeTree']>,
1408
+ >(opts?: {
1409
+ select: (state: RouterState<RegisteredRouter['routeTree']>) => TSelected
1410
+ }): TSelected {
1411
+ const { state } = useRouter()
1412
+ // return useStore(router.__store, opts?.select as any)
1413
+ return opts?.select ? opts.select(state) : (state as any)
1414
+ }
1415
+
1416
+ export type RouterProps<
1417
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
1418
+ TDehydrated extends Record<string, any> = Record<string, any>,
1419
+ > = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> & {
1420
+ router: Router<TRouteTree>
1421
+ context?: Partial<RouterOptions<TRouteTree, TDehydrated>['context']>
1422
+ }
1423
+
1424
+ export function useRouter<
1425
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
1426
+ >(): RouterContext<TRouteTree> {
1427
+ const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext
1428
+ const value = React.useContext(resolvedContext)
1429
+ warning(value, 'useRouter must be used inside a <RouterProvider> component!')
1430
+ return value as any
1431
+ }
1432
+ export interface RouteMatch<
1433
+ TRouteTree extends AnyRoute = AnyRoute,
1434
+ TRouteId extends RouteIds<TRouteTree> = ParseRoute<TRouteTree>['id'],
1435
+ > {
1436
+ id: string
1437
+ routeId: TRouteId
1438
+ pathname: string
1439
+ params: RouteById<TRouteTree, TRouteId>['types']['allParams']
1440
+ status: 'pending' | 'success' | 'error'
1441
+ isFetching: boolean
1442
+ invalid: boolean
1443
+ error: unknown
1444
+ paramsError: unknown
1445
+ searchError: unknown
1446
+ updatedAt: number
1447
+ loadPromise?: Promise<void>
1448
+ __resolveLoadPromise?: () => void
1449
+ context: RouteById<TRouteTree, TRouteId>['types']['allContext']
1450
+ routeSearch: RouteById<TRouteTree, TRouteId>['types']['searchSchema']
1451
+ search: FullSearchSchema<TRouteTree> &
1452
+ RouteById<TRouteTree, TRouteId>['types']['fullSearchSchema']
1453
+ fetchedAt: number
1454
+ abortController: AbortController
1455
+ }
1456
+
1457
+ export type AnyRouteMatch = RouteMatch<any>