@tanstack/react-router 1.78.3 → 1.81.0

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 (123) hide show
  1. package/dist/cjs/Match.cjs +10 -11
  2. package/dist/cjs/Match.cjs.map +1 -1
  3. package/dist/cjs/Matches.cjs +8 -4
  4. package/dist/cjs/Matches.cjs.map +1 -1
  5. package/dist/cjs/Matches.d.cts +8 -9
  6. package/dist/cjs/RouterProvider.cjs.map +1 -1
  7. package/dist/cjs/RouterProvider.d.cts +3 -3
  8. package/dist/cjs/Transitioner.cjs +9 -8
  9. package/dist/cjs/Transitioner.cjs.map +1 -1
  10. package/dist/cjs/fileRoute.cjs +15 -3
  11. package/dist/cjs/fileRoute.cjs.map +1 -1
  12. package/dist/cjs/fileRoute.d.cts +12 -19
  13. package/dist/cjs/link.cjs +4 -1
  14. package/dist/cjs/link.cjs.map +1 -1
  15. package/dist/cjs/link.d.cts +1 -1
  16. package/dist/cjs/path.cjs +12 -2
  17. package/dist/cjs/path.cjs.map +1 -1
  18. package/dist/cjs/path.d.cts +2 -1
  19. package/dist/cjs/route.cjs +33 -9
  20. package/dist/cjs/route.cjs.map +1 -1
  21. package/dist/cjs/route.d.cts +20 -38
  22. package/dist/cjs/router.cjs +15 -4
  23. package/dist/cjs/router.cjs.map +1 -1
  24. package/dist/cjs/router.d.cts +25 -11
  25. package/dist/cjs/routerContext.cjs.map +1 -1
  26. package/dist/cjs/routerContext.d.cts +1 -1
  27. package/dist/cjs/structuralSharing.d.cts +12 -0
  28. package/dist/cjs/useLoaderData.cjs +4 -2
  29. package/dist/cjs/useLoaderData.cjs.map +1 -1
  30. package/dist/cjs/useLoaderData.d.cts +12 -9
  31. package/dist/cjs/useLoaderDeps.cjs +3 -2
  32. package/dist/cjs/useLoaderDeps.cjs.map +1 -1
  33. package/dist/cjs/useLoaderDeps.d.cts +12 -8
  34. package/dist/cjs/useLocation.cjs.map +1 -1
  35. package/dist/cjs/useLocation.d.cts +6 -3
  36. package/dist/cjs/useMatch.cjs +2 -1
  37. package/dist/cjs/useMatch.cjs.map +1 -1
  38. package/dist/cjs/useMatch.d.cts +10 -8
  39. package/dist/cjs/useParams.cjs +3 -1
  40. package/dist/cjs/useParams.cjs.map +1 -1
  41. package/dist/cjs/useParams.d.cts +12 -8
  42. package/dist/cjs/useRouteContext.cjs.map +1 -1
  43. package/dist/cjs/useRouteContext.d.cts +11 -8
  44. package/dist/cjs/useRouterState.cjs +18 -1
  45. package/dist/cjs/useRouterState.cjs.map +1 -1
  46. package/dist/cjs/useRouterState.d.cts +6 -3
  47. package/dist/cjs/useSearch.cjs +3 -1
  48. package/dist/cjs/useSearch.cjs.map +1 -1
  49. package/dist/cjs/useSearch.d.cts +12 -8
  50. package/dist/cjs/utils.cjs.map +1 -1
  51. package/dist/cjs/utils.d.cts +10 -4
  52. package/dist/esm/Match.js +10 -11
  53. package/dist/esm/Match.js.map +1 -1
  54. package/dist/esm/Matches.d.ts +8 -9
  55. package/dist/esm/Matches.js +8 -4
  56. package/dist/esm/Matches.js.map +1 -1
  57. package/dist/esm/RouterProvider.d.ts +3 -3
  58. package/dist/esm/RouterProvider.js.map +1 -1
  59. package/dist/esm/Transitioner.js +10 -9
  60. package/dist/esm/Transitioner.js.map +1 -1
  61. package/dist/esm/fileRoute.d.ts +12 -19
  62. package/dist/esm/fileRoute.js +15 -3
  63. package/dist/esm/fileRoute.js.map +1 -1
  64. package/dist/esm/link.d.ts +1 -1
  65. package/dist/esm/link.js +4 -1
  66. package/dist/esm/link.js.map +1 -1
  67. package/dist/esm/path.d.ts +2 -1
  68. package/dist/esm/path.js +12 -2
  69. package/dist/esm/path.js.map +1 -1
  70. package/dist/esm/route.d.ts +20 -38
  71. package/dist/esm/route.js +33 -9
  72. package/dist/esm/route.js.map +1 -1
  73. package/dist/esm/router.d.ts +25 -11
  74. package/dist/esm/router.js +15 -4
  75. package/dist/esm/router.js.map +1 -1
  76. package/dist/esm/routerContext.d.ts +1 -1
  77. package/dist/esm/routerContext.js.map +1 -1
  78. package/dist/esm/structuralSharing.d.ts +12 -0
  79. package/dist/esm/useLoaderData.d.ts +12 -9
  80. package/dist/esm/useLoaderData.js +4 -2
  81. package/dist/esm/useLoaderData.js.map +1 -1
  82. package/dist/esm/useLoaderDeps.d.ts +12 -8
  83. package/dist/esm/useLoaderDeps.js +3 -2
  84. package/dist/esm/useLoaderDeps.js.map +1 -1
  85. package/dist/esm/useLocation.d.ts +6 -3
  86. package/dist/esm/useLocation.js.map +1 -1
  87. package/dist/esm/useMatch.d.ts +10 -8
  88. package/dist/esm/useMatch.js +2 -1
  89. package/dist/esm/useMatch.js.map +1 -1
  90. package/dist/esm/useParams.d.ts +12 -8
  91. package/dist/esm/useParams.js +3 -1
  92. package/dist/esm/useParams.js.map +1 -1
  93. package/dist/esm/useRouteContext.d.ts +11 -8
  94. package/dist/esm/useRouteContext.js.map +1 -1
  95. package/dist/esm/useRouterState.d.ts +6 -3
  96. package/dist/esm/useRouterState.js +18 -1
  97. package/dist/esm/useRouterState.js.map +1 -1
  98. package/dist/esm/useSearch.d.ts +12 -8
  99. package/dist/esm/useSearch.js +3 -1
  100. package/dist/esm/useSearch.js.map +1 -1
  101. package/dist/esm/utils.d.ts +10 -4
  102. package/dist/esm/utils.js.map +1 -1
  103. package/package.json +3 -3
  104. package/src/Match.tsx +6 -7
  105. package/src/Matches.tsx +52 -24
  106. package/src/RouterProvider.tsx +4 -1
  107. package/src/Transitioner.tsx +9 -10
  108. package/src/fileRoute.ts +29 -29
  109. package/src/link.tsx +6 -3
  110. package/src/path.ts +16 -1
  111. package/src/route.ts +57 -101
  112. package/src/router.ts +60 -4
  113. package/src/routerContext.tsx +1 -1
  114. package/src/structuralSharing.ts +49 -0
  115. package/src/useLoaderData.tsx +76 -28
  116. package/src/useLoaderDeps.tsx +55 -20
  117. package/src/useLocation.tsx +30 -8
  118. package/src/useMatch.tsx +71 -21
  119. package/src/useParams.tsx +70 -21
  120. package/src/useRouteContext.ts +45 -23
  121. package/src/useRouterState.tsx +45 -6
  122. package/src/useSearch.tsx +69 -20
  123. package/src/utils.ts +16 -3
package/src/Match.tsx CHANGED
@@ -120,9 +120,10 @@ export const MatchInner = React.memo(function MatchInnerImpl({
120
120
  return {
121
121
  routeId,
122
122
  matchIndex,
123
- match: pick(match, ['id', 'status', 'error', 'loadPromise']),
123
+ match: pick(match, ['id', 'status', 'error']),
124
124
  }
125
125
  },
126
+ structuralSharing: true as any,
126
127
  })
127
128
 
128
129
  const route = router.routesById[routeId]!
@@ -180,7 +181,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
180
181
  // false,
181
182
  // 'Tried to render a redirected route match! This is a weird circumstance, please file an issue!',
182
183
  // )
183
- throw match.loadPromise
184
+ throw router.getMatch(match.id)?.loadPromise
184
185
  }
185
186
 
186
187
  if (match.status === 'error') {
@@ -237,7 +238,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
237
238
  }, pendingMinMs)
238
239
  }
239
240
  }
240
- throw match.loadPromise
241
+ throw router.getMatch(match.id)?.loadPromise
241
242
  }
242
243
 
243
244
  return (
@@ -259,7 +260,7 @@ export const Outlet = React.memo(function OutletImpl() {
259
260
 
260
261
  const route = router.routesById[routeId]!
261
262
 
262
- const { parentGlobalNotFound } = useRouterState({
263
+ const parentGlobalNotFound = useRouterState({
263
264
  select: (s) => {
264
265
  const matches = s.matches
265
266
  const parentMatch = matches.find((d) => d.id === matchId)
@@ -267,9 +268,7 @@ export const Outlet = React.memo(function OutletImpl() {
267
268
  parentMatch,
268
269
  `Could not find parent match for matchId "${matchId}"`,
269
270
  )
270
- return {
271
- parentGlobalNotFound: parentMatch.globalNotFound,
272
- }
271
+ return parentMatch.globalNotFound
273
272
  },
274
273
  })
275
274
 
package/src/Matches.tsx CHANGED
@@ -7,8 +7,12 @@ import { Transitioner } from './Transitioner'
7
7
  import { matchContext } from './matchContext'
8
8
  import { Match } from './Match'
9
9
  import { SafeFragment } from './SafeFragment'
10
+ import type {
11
+ StructuralSharingOption,
12
+ ValidateSelected,
13
+ } from './structuralSharing'
10
14
  import type { AnyRoute, ReactNode, StaticDataRouteOption } from './route'
11
- import type { AnyRouter, RegisteredRouter } from './router'
15
+ import type { AnyRouter, RegisteredRouter, RouterState } from './router'
12
16
  import type { ResolveRelativePath, ToOptions } from './link'
13
17
  import type {
14
18
  AllContext,
@@ -288,6 +292,7 @@ export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
288
292
 
289
293
  useRouterState({
290
294
  select: (s) => [s.location.href, s.resolvedLocation.href, s.status],
295
+ structuralSharing: true as any,
291
296
  })
292
297
 
293
298
  return React.useCallback(
@@ -369,56 +374,79 @@ export type MakeRouteMatchUnion<
369
374
  >
370
375
  : never
371
376
 
377
+ export interface UseMatchesBaseOptions<
378
+ TRouter extends AnyRouter,
379
+ TSelected,
380
+ TStructuralSharing,
381
+ > {
382
+ select?: (
383
+ matches: Array<MakeRouteMatchUnion<TRouter>>,
384
+ ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
385
+ }
386
+
387
+ export type UseMatchesResult<
388
+ TRouter extends AnyRouter,
389
+ TSelected,
390
+ > = unknown extends TSelected ? Array<MakeRouteMatchUnion<TRouter>> : TSelected
391
+
372
392
  export function useMatches<
373
393
  TRouter extends AnyRouter = RegisteredRouter,
374
- TRouteMatch = MakeRouteMatchUnion<TRouter>,
375
- T = Array<TRouteMatch>,
376
- >(opts?: { select?: (matches: Array<TRouteMatch>) => T }): T {
394
+ TSelected = unknown,
395
+ TStructuralSharing extends boolean = boolean,
396
+ >(
397
+ opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
398
+ StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
399
+ ): UseMatchesResult<TRouter, TSelected> {
377
400
  return useRouterState({
378
- select: (state) => {
401
+ select: (state: RouterState<TRouter['routeTree']>) => {
379
402
  const matches = state.matches
380
403
  return opts?.select
381
- ? opts.select(matches as Array<TRouteMatch>)
382
- : (matches as T)
404
+ ? opts.select(matches as Array<MakeRouteMatchUnion<TRouter>>)
405
+ : matches
383
406
  },
384
- })
407
+ structuralSharing: opts?.structuralSharing,
408
+ } as any) as UseMatchesResult<TRouter, TSelected>
385
409
  }
386
410
 
387
411
  export function useParentMatches<
388
412
  TRouter extends AnyRouter = RegisteredRouter,
389
- TRouteMatch = MakeRouteMatchUnion<TRouter>,
390
- T = Array<TRouteMatch>,
391
- >(opts?: { select?: (matches: Array<TRouteMatch>) => T }): T {
413
+ TSelected = unknown,
414
+ TStructuralSharing extends boolean = boolean,
415
+ >(
416
+ opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
417
+ StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
418
+ ): UseMatchesResult<TRouter, TSelected> {
392
419
  const contextMatchId = React.useContext(matchContext)
393
420
 
394
421
  return useMatches({
395
- select: (matches) => {
422
+ select: (matches: Array<MakeRouteMatchUnion<TRouter>>) => {
396
423
  matches = matches.slice(
397
424
  0,
398
425
  matches.findIndex((d) => d.id === contextMatchId),
399
426
  )
400
- return opts?.select
401
- ? opts.select(matches as Array<TRouteMatch>)
402
- : (matches as T)
427
+ return opts?.select ? opts.select(matches) : matches
403
428
  },
404
- })
429
+ structuralSharing: opts?.structuralSharing,
430
+ } as any)
405
431
  }
406
432
 
407
433
  export function useChildMatches<
408
434
  TRouter extends AnyRouter = RegisteredRouter,
409
- TRouteMatch = MakeRouteMatchUnion<TRouter>,
410
- T = Array<TRouteMatch>,
411
- >(opts?: { select?: (matches: Array<TRouteMatch>) => T }): T {
435
+ TSelected = unknown,
436
+ TStructuralSharing extends boolean = boolean,
437
+ >(
438
+ opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
439
+ StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
440
+ ): UseMatchesResult<TRouter, TSelected> {
412
441
  const contextMatchId = React.useContext(matchContext)
413
442
 
414
443
  return useMatches({
415
- select: (matches) => {
444
+ select: (matches: Array<MakeRouteMatchUnion<TRouter>>) => {
416
445
  matches = matches.slice(
417
446
  matches.findIndex((d) => d.id === contextMatchId) + 1,
418
447
  )
419
- return opts?.select
420
- ? opts.select(matches as Array<TRouteMatch>)
421
- : (matches as T)
448
+ return opts?.select ? opts.select(matches) : matches
422
449
  },
423
- })
450
+ structuralSharing: opts?.structuralSharing,
451
+ } as any)
424
452
  }
@@ -105,18 +105,21 @@ export type RouterProps<
105
105
  RouterOptions<
106
106
  TRouter['routeTree'],
107
107
  NonNullable<TRouter['options']['trailingSlash']>,
108
+ NonNullable<TRouter['options']['defaultStructuralSharing']>,
108
109
  TDehydrated
109
110
  >,
110
111
  'context'
111
112
  > & {
112
113
  router: Router<
113
114
  TRouter['routeTree'],
114
- NonNullable<TRouter['options']['trailingSlash']>
115
+ NonNullable<TRouter['options']['trailingSlash']>,
116
+ NonNullable<TRouter['options']['defaultStructuralSharing']>
115
117
  >
116
118
  context?: Partial<
117
119
  RouterOptions<
118
120
  TRouter['routeTree'],
119
121
  NonNullable<TRouter['options']['trailingSlash']>,
122
+ NonNullable<TRouter['options']['defaultStructuralSharing']>,
120
123
  TDehydrated
121
124
  >['context']
122
125
  >
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react'
2
- import { pick, useLayoutEffect, usePrevious } from './utils'
2
+ import { useLayoutEffect, usePrevious } from './utils'
3
3
  import { useRouter } from './useRouter'
4
4
  import { useRouterState } from './useRouterState'
5
5
  import { trimPathRight } from './path'
@@ -7,24 +7,23 @@ import { trimPathRight } from './path'
7
7
  export function Transitioner() {
8
8
  const router = useRouter()
9
9
  const mountLoadForRouter = React.useRef({ router, mounted: false })
10
- const routerState = useRouterState({
11
- select: (s) =>
12
- pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
10
+ const isLoading = useRouterState({
11
+ select: ({ isLoading }) => isLoading,
13
12
  })
14
13
 
15
14
  const [isTransitioning, startReactTransition_] = React.useTransition()
16
15
  // Track pending state changes
17
16
  const hasPendingMatches = useRouterState({
18
17
  select: (s) => s.matches.some((d) => d.status === 'pending'),
18
+ structuralSharing: true,
19
19
  })
20
20
 
21
- const previousIsLoading = usePrevious(routerState.isLoading)
21
+ const previousIsLoading = usePrevious(isLoading)
22
22
 
23
- const isAnyPending =
24
- routerState.isLoading || isTransitioning || hasPendingMatches
23
+ const isAnyPending = isLoading || isTransitioning || hasPendingMatches
25
24
  const previousIsAnyPending = usePrevious(isAnyPending)
26
25
 
27
- const isPagePending = routerState.isLoading || hasPendingMatches
26
+ const isPagePending = isLoading || hasPendingMatches
28
27
  const previousIsPagePending = usePrevious(isPagePending)
29
28
 
30
29
  if (!router.isServer) {
@@ -81,7 +80,7 @@ export function Transitioner() {
81
80
 
82
81
  useLayoutEffect(() => {
83
82
  // The router was loading and now it's not
84
- if (previousIsLoading && !routerState.isLoading) {
83
+ if (previousIsLoading && !isLoading) {
85
84
  const toLocation = router.state.location
86
85
  const fromLocation = router.state.resolvedLocation
87
86
  const pathChanged = fromLocation.pathname !== toLocation.pathname
@@ -93,7 +92,7 @@ export function Transitioner() {
93
92
  pathChanged,
94
93
  })
95
94
  }
96
- }, [previousIsLoading, router, routerState.isLoading])
95
+ }, [previousIsLoading, router, isLoading])
97
96
 
98
97
  useLayoutEffect(() => {
99
98
  // emit onBeforeRouteMount
package/src/fileRoute.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import warning from 'tiny-warning'
2
2
  import { createRoute } from './route'
3
+
3
4
  import { useMatch } from './useMatch'
4
5
  import { useLoaderDeps } from './useLoaderDeps'
5
6
  import { useLoaderData } from './useLoaderData'
6
7
  import { useSearch } from './useSearch'
7
8
  import { useParams } from './useParams'
8
9
  import { useNavigate } from './useNavigate'
10
+ import type { UseParamsRoute } from './useParams'
11
+ import type { UseMatchRoute } from './useMatch'
12
+ import type { UseSearchRoute } from './useSearch'
9
13
  import type { Constrain } from './utils'
10
14
  import type {
11
15
  AnyContext,
@@ -20,9 +24,11 @@ import type {
20
24
  RouteLoaderFn,
21
25
  UpdatableRouteOptions,
22
26
  } from './route'
23
- import type { MakeRouteMatch } from './Matches'
24
27
  import type { RegisteredRouter } from './router'
25
28
  import type { RouteById, RouteIds } from './routeInfo'
29
+ import type { UseLoaderDepsRoute } from './useLoaderDeps'
30
+ import type { UseLoaderDataRoute } from './useLoaderData'
31
+ import type { UseRouteContextRoute } from './useRouteContext'
26
32
 
27
33
  export interface FileRoutesByPath {
28
34
  // '/': {
@@ -208,48 +214,42 @@ export class LazyRoute<TRoute extends AnyRoute> {
208
214
  ;(this as any).$$typeof = Symbol.for('react.memo')
209
215
  }
210
216
 
211
- useMatch = <
212
- TRouteMatch = MakeRouteMatch<
213
- RegisteredRouter['routeTree'],
214
- TRoute['types']['id']
215
- >,
216
- TSelected = TRouteMatch,
217
- >(opts?: {
218
- select?: (match: TRouteMatch) => TSelected
219
- }): TSelected => {
220
- return useMatch({ select: opts?.select, from: this.options.id })
217
+ useMatch: UseMatchRoute<TRoute['id']> = (opts) => {
218
+ return useMatch({
219
+ select: opts?.select,
220
+ from: this.options.id,
221
+ structuralSharing: opts?.structuralSharing,
222
+ } as any) as any
221
223
  }
222
224
 
223
- useRouteContext = <TSelected = TRoute['types']['allContext']>(opts?: {
224
- select?: (s: TRoute['types']['allContext']) => TSelected
225
- }): TSelected => {
225
+ useRouteContext: UseRouteContextRoute<TRoute['id']> = (opts) => {
226
226
  return useMatch({
227
227
  from: this.options.id,
228
228
  select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
229
- })
229
+ }) as any
230
230
  }
231
231
 
232
- useSearch = <TSelected = TRoute['types']['fullSearchSchema']>(opts?: {
233
- select?: (s: TRoute['types']['fullSearchSchema']) => TSelected
234
- }): TSelected => {
235
- return useSearch({ ...opts, from: this.options.id })
232
+ useSearch: UseSearchRoute<TRoute['id']> = (opts) => {
233
+ return useSearch({
234
+ select: opts?.select,
235
+ structuralSharing: opts?.structuralSharing,
236
+ from: this.options.id,
237
+ } as any)
236
238
  }
237
239
 
238
- useParams = <TSelected = TRoute['types']['allParams']>(opts?: {
239
- select?: (s: TRoute['types']['allParams']) => TSelected
240
- }): TSelected => {
241
- return useParams({ ...opts, from: this.options.id })
240
+ useParams: UseParamsRoute<TRoute['id']> = (opts) => {
241
+ return useParams({
242
+ select: opts?.select,
243
+ structuralSharing: opts?.structuralSharing,
244
+ from: this.options.id,
245
+ } as any)
242
246
  }
243
247
 
244
- useLoaderDeps = <TSelected = TRoute['types']['loaderDeps']>(opts?: {
245
- select?: (s: TRoute['types']['loaderDeps']) => TSelected
246
- }): TSelected => {
248
+ useLoaderDeps: UseLoaderDepsRoute<TRoute['id']> = (opts) => {
247
249
  return useLoaderDeps({ ...opts, from: this.options.id } as any)
248
250
  }
249
251
 
250
- useLoaderData = <TSelected = TRoute['types']['loaderData']>(opts?: {
251
- select?: (s: TRoute['types']['loaderData']) => TSelected
252
- }): TSelected => {
252
+ useLoaderData: UseLoaderDataRoute<TRoute['id']> = (opts) => {
253
253
  return useLoaderData({ ...opts, from: this.options.id } as any)
254
254
  }
255
255
 
package/src/link.tsx CHANGED
@@ -233,10 +233,10 @@ export interface MaskOptions<
233
233
  }
234
234
 
235
235
  export type ToMaskOptions<
236
- TRouteTree extends AnyRouter = RegisteredRouter,
236
+ TRouter extends AnyRouter = RegisteredRouter,
237
237
  TMaskFrom extends string = string,
238
238
  TMaskTo extends string = '.',
239
- > = ToSubOptions<TRouteTree, TMaskFrom, TMaskTo> & {
239
+ > = ToSubOptions<TRouter, TMaskFrom, TMaskTo> & {
240
240
  unmaskOnReload?: boolean
241
241
  }
242
242
 
@@ -645,7 +645,10 @@ export function useLinkProps<
645
645
  }, [to])
646
646
 
647
647
  // subscribe to search params to re-build location if it changes
648
- const currentSearch = useRouterState({ select: (s) => s.location.search })
648
+ const currentSearch = useRouterState({
649
+ select: (s) => s.location.search,
650
+ structuralSharing: true as any,
651
+ })
649
652
 
650
653
  const next = React.useMemo(
651
654
  () => router.buildLocation(options as any),
package/src/path.ts CHANGED
@@ -204,6 +204,8 @@ interface InterpolatePathOptions {
204
204
  params: Record<string, unknown>
205
205
  leaveWildcards?: boolean
206
206
  leaveParams?: boolean
207
+ // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params
208
+ decodeCharMap?: Map<string, string>
207
209
  }
208
210
 
209
211
  export function interpolatePath({
@@ -211,6 +213,7 @@ export function interpolatePath({
211
213
  params,
212
214
  leaveWildcards,
213
215
  leaveParams,
216
+ decodeCharMap,
214
217
  }: InterpolatePathOptions) {
215
218
  const interpolatedPathSegments = parsePathname(path)
216
219
  const encodedParams: any = {}
@@ -222,7 +225,9 @@ export function interpolatePath({
222
225
  // the splat/catch-all routes shouldn't have the '/' encoded out
223
226
  encodedParams[key] = isValueString ? encodeURI(value) : value
224
227
  } else {
225
- encodedParams[key] = isValueString ? encodeURIComponent(value) : value
228
+ encodedParams[key] = isValueString
229
+ ? encodePathParam(value, decodeCharMap)
230
+ : value
226
231
  }
227
232
  }
228
233
 
@@ -247,6 +252,16 @@ export function interpolatePath({
247
252
  )
248
253
  }
249
254
 
255
+ function encodePathParam(value: string, decodeCharMap?: Map<string, string>) {
256
+ let encoded = encodeURIComponent(value)
257
+ if (decodeCharMap) {
258
+ for (const [encodedChar, char] of decodeCharMap) {
259
+ encoded = encoded.replaceAll(encodedChar, char)
260
+ }
261
+ }
262
+ return encoded
263
+ }
264
+
250
265
  export function matchPathname(
251
266
  basepath: string,
252
267
  currentPathname: string,
package/src/route.ts CHANGED
@@ -1,18 +1,22 @@
1
1
  import invariant from 'tiny-invariant'
2
- import { useMatch } from './useMatch'
3
- import { useLoaderDeps } from './useLoaderDeps'
4
- import { useLoaderData } from './useLoaderData'
5
2
  import { joinPaths, trimPathLeft } from './path'
3
+ import { useLoaderData } from './useLoaderData'
4
+ import { useLoaderDeps } from './useLoaderDeps'
6
5
  import { useParams } from './useParams'
7
6
  import { useSearch } from './useSearch'
8
7
  import { notFound } from './not-found'
9
8
  import { useNavigate } from './useNavigate'
10
9
  import { rootRouteId } from './root'
10
+ import { useMatch } from './useMatch'
11
+ import type { UseLoaderDataRoute } from './useLoaderData'
12
+ import type { UseMatchRoute } from './useMatch'
13
+ import type { UseLoaderDepsRoute } from './useLoaderDeps'
14
+ import type { UseParamsRoute } from './useParams'
15
+ import type { UseSearchRoute } from './useSearch'
11
16
  import type * as React from 'react'
12
17
  import type { RootRouteId } from './root'
13
18
  import type { UseNavigateResult } from './useNavigate'
14
19
  import type {
15
- MakeRouteMatch,
16
20
  MakeRouteMatchFromRoute,
17
21
  MakeRouteMatchUnion,
18
22
  RouteMatch,
@@ -25,6 +29,7 @@ import type { Assign, Constrain, Expand, NoInfer } from './utils'
25
29
  import type { BuildLocationFn, NavigateFn } from './RouterProvider'
26
30
  import type { NotFoundError } from './not-found'
27
31
  import type { LazyRoute } from './fileRoute'
32
+ import type { UseRouteContextRoute } from './useRouteContext'
28
33
 
29
34
  export type AnyPathParams = {}
30
35
 
@@ -845,60 +850,42 @@ export class RouteApi<TId, TRouter extends AnyRouter = RegisteredRouter> {
845
850
  this.id = id as any
846
851
  }
847
852
 
848
- useMatch = <
849
- TRouteTree extends AnyRoute = TRouter['routeTree'],
850
- TRouteMatch = MakeRouteMatch<TRouteTree, TId>,
851
- TSelected = TRouteMatch,
852
- >(opts?: {
853
- select?: (match: TRouteMatch) => TSelected
854
- }): TSelected => {
855
- return useMatch({ select: opts?.select, from: this.id })
853
+ useMatch: UseMatchRoute<TId> = (opts) => {
854
+ return useMatch({
855
+ select: opts?.select,
856
+ from: this.id,
857
+ structuralSharing: opts?.structuralSharing,
858
+ } as any) as any
856
859
  }
857
860
 
858
- useRouteContext = <
859
- TSelected = Expand<RouteTypesById<TRouter, TId>['allContext']>,
860
- >(opts?: {
861
- select?: (
862
- s: Expand<RouteTypesById<TRouter, TId>['allContext']>,
863
- ) => TSelected
864
- }): TSelected => {
861
+ useRouteContext: UseRouteContextRoute<TId> = (opts) => {
865
862
  return useMatch({
866
- from: this.id,
867
- select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
868
- })
863
+ from: this.id as any,
864
+ select: (d) => (opts?.select ? opts.select(d.context) : d.context),
865
+ }) as any
869
866
  }
870
867
 
871
- useSearch = <
872
- TSelected = Expand<RouteTypesById<TRouter, TId>['fullSearchSchema']>,
873
- >(opts?: {
874
- select?: (
875
- s: Expand<RouteTypesById<TRouter, TId>['fullSearchSchema']>,
876
- ) => TSelected
877
- }): TSelected => {
878
- return useSearch({ ...opts, from: this.id })
868
+ useSearch: UseSearchRoute<TId> = (opts) => {
869
+ return useSearch({
870
+ select: opts?.select,
871
+ structuralSharing: opts?.structuralSharing,
872
+ from: this.id,
873
+ } as any)
879
874
  }
880
875
 
881
- useParams = <
882
- TSelected = Expand<RouteTypesById<TRouter, TId>['allParams']>,
883
- >(opts?: {
884
- select?: (s: Expand<RouteTypesById<TRouter, TId>['allParams']>) => TSelected
885
- }): TSelected => {
886
- return useParams({ ...opts, from: this.id })
876
+ useParams: UseParamsRoute<TId> = (opts) => {
877
+ return useParams({
878
+ select: opts?.select,
879
+ structuralSharing: opts?.structuralSharing,
880
+ from: this.id,
881
+ } as any)
887
882
  }
888
883
 
889
- useLoaderDeps = <
890
- TSelected = RouteTypesById<TRouter, TId>['loaderDeps'],
891
- >(opts?: {
892
- select?: (s: RouteTypesById<TRouter, TId>['loaderDeps']) => TSelected
893
- }): TSelected => {
884
+ useLoaderDeps: UseLoaderDepsRoute<TId> = (opts) => {
894
885
  return useLoaderDeps({ ...opts, from: this.id, strict: false } as any)
895
886
  }
896
887
 
897
- useLoaderData = <
898
- TSelected = RouteTypesById<TRouter, TId>['loaderData'],
899
- >(opts?: {
900
- select?: (s: RouteTypesById<TRouter, TId>['loaderData']) => TSelected
901
- }): TSelected => {
888
+ useLoaderData: UseLoaderDataRoute<TId> = (opts) => {
902
889
  return useLoaderData({ ...opts, from: this.id, strict: false } as any)
903
890
  }
904
891
 
@@ -1258,74 +1245,43 @@ export class Route<
1258
1245
  return this
1259
1246
  }
1260
1247
 
1261
- useMatch = <
1262
- TRouter extends AnyRouter = RegisteredRouter,
1263
- TRouteTree extends AnyRoute = TRouter['routeTree'],
1264
- TRouteMatch = MakeRouteMatch<TRouteTree, TId>,
1265
- TSelected = TRouteMatch,
1266
- >(opts?: {
1267
- select?: (match: TRouteMatch) => TSelected
1268
- }): TSelected => {
1269
- return useMatch({ ...opts, from: this.id })
1248
+ useMatch: UseMatchRoute<TId> = (opts) => {
1249
+ return useMatch({
1250
+ select: opts?.select,
1251
+ from: this.id,
1252
+ structuralSharing: opts?.structuralSharing,
1253
+ } as any) as any
1270
1254
  }
1271
1255
 
1272
- useRouteContext = <
1273
- TSelected = Expand<
1274
- ResolveAllContext<
1275
- TParentRoute,
1276
- TRouterContext,
1277
- TRouteContextFn,
1278
- TBeforeLoadFn
1279
- >
1280
- >,
1281
- >(opts?: {
1282
- select?: (
1283
- search: Expand<
1284
- ResolveAllContext<
1285
- TParentRoute,
1286
- TRouterContext,
1287
- TRouteContextFn,
1288
- TBeforeLoadFn
1289
- >
1290
- >,
1291
- ) => TSelected
1292
- }): TSelected => {
1256
+ useRouteContext: UseRouteContextRoute<TId> = (opts?) => {
1293
1257
  return useMatch({
1294
1258
  ...opts,
1295
1259
  from: this.id,
1296
- select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
1297
- })
1260
+ select: (d) => (opts?.select ? opts.select(d.context) : d.context),
1261
+ }) as any
1298
1262
  }
1299
1263
 
1300
- useSearch = <
1301
- TSelected = Expand<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>,
1302
- >(opts?: {
1303
- select?: (
1304
- search: Expand<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>,
1305
- ) => TSelected
1306
- }): TSelected => {
1307
- return useSearch({ ...opts, from: this.id })
1264
+ useSearch: UseSearchRoute<TId> = (opts) => {
1265
+ return useSearch({
1266
+ select: opts?.select,
1267
+ structuralSharing: opts?.structuralSharing,
1268
+ from: this.id,
1269
+ } as any)
1308
1270
  }
1309
1271
 
1310
- useParams = <
1311
- TSelected = Expand<ResolveAllParamsFromParent<TParentRoute, TParams>>,
1312
- >(opts?: {
1313
- select?: (
1314
- search: Expand<ResolveAllParamsFromParent<TParentRoute, TParams>>,
1315
- ) => TSelected
1316
- }): TSelected => {
1317
- return useParams({ ...opts, from: this.id })
1272
+ useParams: UseParamsRoute<TId> = (opts) => {
1273
+ return useParams({
1274
+ select: opts?.select,
1275
+ structuralSharing: opts?.structuralSharing,
1276
+ from: this.id,
1277
+ } as any)
1318
1278
  }
1319
1279
 
1320
- useLoaderDeps = <TSelected = TLoaderDeps>(opts?: {
1321
- select?: (s: TLoaderDeps) => TSelected
1322
- }): TSelected => {
1280
+ useLoaderDeps: UseLoaderDepsRoute<TId> = (opts) => {
1323
1281
  return useLoaderDeps({ ...opts, from: this.id } as any)
1324
1282
  }
1325
1283
 
1326
- useLoaderData = <TSelected = ResolveLoaderData<TLoaderFn>>(opts?: {
1327
- select?: (search: ResolveLoaderData<TLoaderFn>) => TSelected
1328
- }): TSelected => {
1284
+ useLoaderData: UseLoaderDataRoute<TId> = (opts) => {
1329
1285
  return useLoaderData({ ...opts, from: this.id } as any)
1330
1286
  }
1331
1287
 
@@ -1643,7 +1599,7 @@ export function createRouteMask<
1643
1599
  >(
1644
1600
  opts: {
1645
1601
  routeTree: TRouteTree
1646
- } & ToMaskOptions<Router<TRouteTree, 'never'>, TFrom, TTo>,
1602
+ } & ToMaskOptions<Router<TRouteTree, 'never', boolean>, TFrom, TTo>,
1647
1603
  ): RouteMask<TRouteTree> {
1648
1604
  return opts as any
1649
1605
  }