@tanstack/react-router 1.120.4 → 1.121.0-alpha.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.
Files changed (56) hide show
  1. package/dist/cjs/HeadContent.cjs +23 -6
  2. package/dist/cjs/HeadContent.cjs.map +1 -1
  3. package/dist/cjs/Match.cjs +6 -1
  4. package/dist/cjs/Match.cjs.map +1 -1
  5. package/dist/cjs/Matches.cjs.map +1 -1
  6. package/dist/cjs/Matches.d.cts +2 -2
  7. package/dist/cjs/RouterProvider.cjs +10 -8
  8. package/dist/cjs/RouterProvider.cjs.map +1 -1
  9. package/dist/cjs/Scripts.cjs +2 -1
  10. package/dist/cjs/Scripts.cjs.map +1 -1
  11. package/dist/cjs/fileRoute.cjs +8 -0
  12. package/dist/cjs/fileRoute.cjs.map +1 -1
  13. package/dist/cjs/fileRoute.d.cts +16 -5
  14. package/dist/cjs/index.d.cts +2 -2
  15. package/dist/cjs/link.cjs +9 -5
  16. package/dist/cjs/link.cjs.map +1 -1
  17. package/dist/cjs/router.cjs +8 -0
  18. package/dist/cjs/router.cjs.map +1 -1
  19. package/dist/cjs/useBlocker.cjs +4 -1
  20. package/dist/cjs/useBlocker.cjs.map +1 -1
  21. package/dist/cjs/useNavigate.cjs +14 -7
  22. package/dist/cjs/useNavigate.cjs.map +1 -1
  23. package/dist/esm/HeadContent.js +23 -6
  24. package/dist/esm/HeadContent.js.map +1 -1
  25. package/dist/esm/Match.js +6 -1
  26. package/dist/esm/Match.js.map +1 -1
  27. package/dist/esm/Matches.d.ts +2 -2
  28. package/dist/esm/Matches.js.map +1 -1
  29. package/dist/esm/RouterProvider.js +10 -8
  30. package/dist/esm/RouterProvider.js.map +1 -1
  31. package/dist/esm/Scripts.js +2 -1
  32. package/dist/esm/Scripts.js.map +1 -1
  33. package/dist/esm/fileRoute.d.ts +16 -5
  34. package/dist/esm/fileRoute.js +8 -0
  35. package/dist/esm/fileRoute.js.map +1 -1
  36. package/dist/esm/index.d.ts +2 -2
  37. package/dist/esm/link.js +9 -5
  38. package/dist/esm/link.js.map +1 -1
  39. package/dist/esm/router.js +8 -0
  40. package/dist/esm/router.js.map +1 -1
  41. package/dist/esm/useBlocker.js +4 -1
  42. package/dist/esm/useBlocker.js.map +1 -1
  43. package/dist/esm/useNavigate.js +14 -7
  44. package/dist/esm/useNavigate.js.map +1 -1
  45. package/package.json +3 -3
  46. package/src/HeadContent.tsx +26 -3
  47. package/src/Match.tsx +15 -4
  48. package/src/Matches.tsx +4 -1
  49. package/src/RouterProvider.tsx +11 -9
  50. package/src/Scripts.tsx +1 -0
  51. package/src/fileRoute.ts +26 -4
  52. package/src/index.tsx +4 -3
  53. package/src/link.tsx +18 -4
  54. package/src/router.ts +9 -0
  55. package/src/useBlocker.tsx +4 -1
  56. package/src/useNavigate.tsx +23 -6
package/src/Matches.tsx CHANGED
@@ -15,6 +15,7 @@ import type { ReactNode } from './route'
15
15
  import type {
16
16
  AnyRouter,
17
17
  DeepPartial,
18
+ Expand,
18
19
  MakeOptionalPathParams,
19
20
  MakeOptionalSearchParams,
20
21
  MakeRouteMatchUnion,
@@ -123,7 +124,9 @@ export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
123
124
  const TMaskTo extends string = '',
124
125
  >(
125
126
  opts: UseMatchRouteOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
126
- ): false | ResolveRoute<TRouter, TFrom, TTo>['types']['allParams'] => {
127
+ ):
128
+ | false
129
+ | Expand<ResolveRoute<TRouter, TFrom, TTo>['types']['allParams']> => {
127
130
  const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts
128
131
 
129
132
  return router.matchRoute(rest as any, {
@@ -17,15 +17,17 @@ export function RouterContextProvider<
17
17
  }: RouterProps<TRouter, TDehydrated> & {
18
18
  children: React.ReactNode
19
19
  }) {
20
- // Allow the router to update options on the router instance
21
- router.update({
22
- ...router.options,
23
- ...rest,
24
- context: {
25
- ...router.options.context,
26
- ...rest.context,
27
- },
28
- } as any)
20
+ if (Object.keys(rest).length > 0) {
21
+ // Allow the router to update options on the router instance
22
+ router.update({
23
+ ...router.options,
24
+ ...rest,
25
+ context: {
26
+ ...router.options.context,
27
+ ...rest.context,
28
+ },
29
+ } as any)
30
+ }
29
31
 
30
32
  const routerContext = getRouterContext()
31
33
 
package/src/Scripts.tsx CHANGED
@@ -50,6 +50,7 @@ export const Scripts = () => {
50
50
  children,
51
51
  })),
52
52
  }),
53
+ structuralSharing: true as any,
53
54
  })
54
55
 
55
56
  const allScripts = [...scripts, ...assetScripts] as Array<RouterManagedTag>
package/src/fileRoute.ts CHANGED
@@ -28,6 +28,7 @@ import type {
28
28
  RouteIds,
29
29
  RouteLoaderFn,
30
30
  UpdatableRouteOptions,
31
+ UseNavigateResult,
31
32
  } from '@tanstack/router-core'
32
33
  import type { UseLoaderDepsRoute } from './useLoaderDeps'
33
34
  import type { UseLoaderDataRoute } from './useLoaderData'
@@ -41,8 +42,13 @@ export function createFileRoute<
41
42
  TFullPath extends
42
43
  RouteConstraints['TFullPath'] = FileRoutesByPath[TFilePath]['fullPath'],
43
44
  >(
44
- path: TFilePath,
45
+ path?: TFilePath,
45
46
  ): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>['createRoute'] {
47
+ if (typeof path === 'object') {
48
+ return new FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>(path, {
49
+ silent: true,
50
+ }).createRoute(path) as any
51
+ }
46
52
  return new FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>(path, {
47
53
  silent: true,
48
54
  }).createRoute
@@ -63,7 +69,7 @@ export class FileRoute<
63
69
  silent?: boolean
64
70
 
65
71
  constructor(
66
- public path: TFilePath,
72
+ public path?: TFilePath,
67
73
  _opts?: { silent: boolean },
68
74
  ) {
69
75
  this.silent = _opts?.silent
@@ -159,6 +165,18 @@ export function FileRouteLoader<
159
165
  return (loaderFn) => loaderFn as any
160
166
  }
161
167
 
168
+ declare module '@tanstack/router-core' {
169
+ export interface LazyRoute<in out TRoute extends AnyRoute> {
170
+ useMatch: UseMatchRoute<TRoute['id']>
171
+ useRouteContext: UseRouteContextRoute<TRoute['id']>
172
+ useSearch: UseSearchRoute<TRoute['id']>
173
+ useParams: UseParamsRoute<TRoute['id']>
174
+ useLoaderDeps: UseLoaderDepsRoute<TRoute['id']>
175
+ useLoaderData: UseLoaderDataRoute<TRoute['id']>
176
+ useNavigate: () => UseNavigateResult<TRoute['fullPath']>
177
+ }
178
+ }
179
+
162
180
  export class LazyRoute<TRoute extends AnyRoute> {
163
181
  options: {
164
182
  id: string
@@ -214,7 +232,7 @@ export class LazyRoute<TRoute extends AnyRoute> {
214
232
  return useLoaderData({ ...opts, from: this.options.id } as any)
215
233
  }
216
234
 
217
- useNavigate = () => {
235
+ useNavigate = (): UseNavigateResult<TRoute['fullPath']> => {
218
236
  const router = useRouter()
219
237
  return useNavigate({ from: router.routesById[this.options.id].fullPath })
220
238
  }
@@ -236,6 +254,10 @@ export function createLazyRoute<
236
254
  export function createLazyFileRoute<
237
255
  TFilePath extends keyof FileRoutesByPath,
238
256
  TRoute extends FileRoutesByPath[TFilePath]['preLoaderRoute'],
239
- >(id: TFilePath) {
257
+ >(id: TFilePath): (opts: LazyRouteOptions) => LazyRoute<TRoute> {
258
+ if (typeof id === 'object') {
259
+ return new LazyRoute<TRoute>(id) as any
260
+ }
261
+
240
262
  return (opts: LazyRouteOptions) => new LazyRoute<TRoute>({ id, ...opts })
241
263
  }
package/src/index.tsx CHANGED
@@ -48,7 +48,6 @@ export type {
48
48
  DeferredPromiseState,
49
49
  DeferredPromise,
50
50
  ParsedLocation,
51
- ParsePathParams,
52
51
  RemoveTrailingSlashes,
53
52
  RemoveLeadingSlashes,
54
53
  ActiveOptions,
@@ -57,6 +56,8 @@ export type {
57
56
  RootRouteId,
58
57
  AnyPathParams,
59
58
  ResolveParams,
59
+ ResolveOptionalParams,
60
+ ResolveRequiredParams,
60
61
  SearchSchemaInput,
61
62
  AnyContext,
62
63
  RouteContext,
@@ -78,8 +79,6 @@ export type {
78
79
  TrimPath,
79
80
  TrimPathLeft,
80
81
  TrimPathRight,
81
- ParseSplatParams,
82
- SplatParams,
83
82
  StringifyParamsFn,
84
83
  ParamsOptions,
85
84
  InferAllParams,
@@ -126,6 +125,7 @@ export type {
126
125
  RouteById,
127
126
  RootRouteOptions,
128
127
  SerializerExtensions,
128
+ CreateFileRoute,
129
129
  } from '@tanstack/router-core'
130
130
 
131
131
  export type * from './serializer'
@@ -237,6 +237,7 @@ export type {
237
237
  RouteConstraints,
238
238
  RouteMask,
239
239
  MatchRouteOptions,
240
+ CreateLazyFileRoute,
240
241
  } from '@tanstack/router-core'
241
242
  export type {
242
243
  UseLinkPropsOptions,
package/src/link.tsx CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  useLayoutEffect,
17
17
  } from './utils'
18
18
 
19
+ import { useMatch } from './useMatch'
19
20
  import { useMatches } from './Matches'
20
21
  import type {
21
22
  AnyRouter,
@@ -105,11 +106,24 @@ export function useLinkProps<
105
106
  structuralSharing: true as any,
106
107
  })
107
108
 
108
- // when `from` is not supplied, use the leaf route of the current matches as the `from` location
109
- // so relative routing works as expected
110
- const from = useMatches({
111
- select: (matches) => options.from ?? matches[matches.length - 1]?.fullPath,
109
+ const isRelativeFromPath = options.relative === 'path'
110
+
111
+ // when `from` is not supplied, use the nearest parent match's full path as the `from` location
112
+ // so relative routing works as expected. Try to stay out of rerenders as much as possible.
113
+ const nearestFrom = useMatch({
114
+ strict: false,
115
+ select: (match) => (isRelativeFromPath ? undefined : match.fullPath),
116
+ })
117
+
118
+ // When no from and relative is path, use the leaf match as the from location
119
+ // Avoid rerenders as much as possible.
120
+ const leafFrom = useMatches({
121
+ select: (matches) =>
122
+ isRelativeFromPath ? matches[matches.length - 1]!.fullPath : undefined,
112
123
  })
124
+
125
+ const from = options.from ?? (isRelativeFromPath ? leafFrom : nearestFrom)
126
+
113
127
  // Use it as the default `from` location
114
128
  const _options = React.useMemo(() => ({ ...options, from }), [options, from])
115
129
 
package/src/router.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RouterCore } from '@tanstack/router-core'
2
+ import { createFileRoute, createLazyFileRoute } from './fileRoute'
2
3
  import type { RouterHistory } from '@tanstack/history'
3
4
  import type {
4
5
  AnyRoute,
@@ -105,3 +106,11 @@ export class Router<
105
106
  super(options)
106
107
  }
107
108
  }
109
+
110
+ if (typeof globalThis !== 'undefined') {
111
+ ;(globalThis as any).createFileRoute = createFileRoute
112
+ ;(globalThis as any).createLazyFileRoute = createLazyFileRoute
113
+ } else if (typeof window !== 'undefined') {
114
+ ;(window as any).createFileRoute = createFileRoute
115
+ ;(window as any).createFileRoute = createLazyFileRoute
116
+ }
@@ -177,7 +177,10 @@ export function useBlocker(
177
177
  location: HistoryLocation,
178
178
  ): AnyShouldBlockFnLocation {
179
179
  const parsedLocation = router.parseLocation(undefined, location)
180
- const matchedRoutes = router.getMatchedRoutes(parsedLocation)
180
+ const matchedRoutes = router.getMatchedRoutes(
181
+ parsedLocation.pathname,
182
+ undefined,
183
+ )
181
184
  if (matchedRoutes.foundRoute === undefined) {
182
185
  throw new Error(`No route found for location ${location.href}`)
183
186
  }
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import { useRouter } from './useRouter'
3
+ import { useMatch } from './useMatch'
3
4
  import type {
4
5
  AnyRouter,
5
6
  FromPathOption,
@@ -14,15 +15,32 @@ export function useNavigate<
14
15
  >(_defaultOpts?: {
15
16
  from?: FromPathOption<TRouter, TDefaultFrom>
16
17
  }): UseNavigateResult<TDefaultFrom> {
17
- const { navigate } = useRouter()
18
+ const { navigate, state } = useRouter()
19
+
20
+ // Just get the index of the current match to avoid rerenders
21
+ // as much as possible
22
+ const matchIndex = useMatch({
23
+ strict: false,
24
+ select: (match) => match.index,
25
+ })
18
26
 
19
27
  return React.useCallback(
20
28
  (options: NavigateOptions) => {
29
+ const isRelativeFromPath = options.relative === 'path'
30
+
31
+ const from =
32
+ options.from ??
33
+ _defaultOpts?.from ??
34
+ (isRelativeFromPath
35
+ ? state.matches[state.matches.length - 1]!.fullPath
36
+ : state.matches[matchIndex]!.fullPath)
37
+
21
38
  return navigate({
22
- from: _defaultOpts?.from,
23
39
  ...options,
40
+ from: from,
24
41
  })
25
42
  },
43
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26
44
  [_defaultOpts?.from, navigate],
27
45
  ) as UseNavigateResult<TDefaultFrom>
28
46
  }
@@ -35,6 +53,7 @@ export function Navigate<
35
53
  const TMaskTo extends string = '',
36
54
  >(props: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>): null {
37
55
  const router = useRouter()
56
+ const navigate = useNavigate()
38
57
 
39
58
  const previousPropsRef = React.useRef<NavigateOptions<
40
59
  TRouter,
@@ -45,11 +64,9 @@ export function Navigate<
45
64
  > | null>(null)
46
65
  React.useEffect(() => {
47
66
  if (previousPropsRef.current !== props) {
48
- router.navigate({
49
- ...props,
50
- })
67
+ navigate(props)
51
68
  previousPropsRef.current = props
52
69
  }
53
- }, [router, props])
70
+ }, [router, props, navigate])
54
71
  return null
55
72
  }