@tanstack/react-router 1.132.0-alpha.2 → 1.132.0-alpha.21

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 +8 -6
  2. package/dist/cjs/HeadContent.cjs.map +1 -1
  3. package/dist/cjs/ScriptOnce.cjs +1 -1
  4. package/dist/cjs/ScriptOnce.cjs.map +1 -1
  5. package/dist/cjs/fileRoute.cjs.map +1 -1
  6. package/dist/cjs/fileRoute.d.cts +2 -2
  7. package/dist/cjs/index.cjs +14 -14
  8. package/dist/cjs/index.d.cts +6 -3
  9. package/dist/cjs/link.cjs +63 -68
  10. package/dist/cjs/link.cjs.map +1 -1
  11. package/dist/cjs/route.cjs.map +1 -1
  12. package/dist/cjs/route.d.cts +11 -11
  13. package/dist/cjs/scroll-restoration.cjs +11 -3
  14. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  15. package/dist/cjs/ssr/serializer.d.cts +6 -0
  16. package/dist/cjs/useBlocker.cjs +1 -1
  17. package/dist/cjs/useBlocker.cjs.map +1 -1
  18. package/dist/cjs/useNavigate.cjs +4 -11
  19. package/dist/cjs/useNavigate.cjs.map +1 -1
  20. package/dist/cjs/useParams.cjs +3 -2
  21. package/dist/cjs/useParams.cjs.map +1 -1
  22. package/dist/esm/HeadContent.js +8 -6
  23. package/dist/esm/HeadContent.js.map +1 -1
  24. package/dist/esm/ScriptOnce.js +1 -1
  25. package/dist/esm/ScriptOnce.js.map +1 -1
  26. package/dist/esm/fileRoute.d.ts +2 -2
  27. package/dist/esm/fileRoute.js.map +1 -1
  28. package/dist/esm/index.d.ts +6 -3
  29. package/dist/esm/index.js +5 -5
  30. package/dist/esm/link.js +63 -68
  31. package/dist/esm/link.js.map +1 -1
  32. package/dist/esm/route.d.ts +11 -11
  33. package/dist/esm/route.js.map +1 -1
  34. package/dist/esm/scroll-restoration.js +11 -3
  35. package/dist/esm/scroll-restoration.js.map +1 -1
  36. package/dist/esm/ssr/serializer.d.ts +6 -0
  37. package/dist/esm/useBlocker.js +1 -1
  38. package/dist/esm/useBlocker.js.map +1 -1
  39. package/dist/esm/useNavigate.js +4 -11
  40. package/dist/esm/useNavigate.js.map +1 -1
  41. package/dist/esm/useParams.js +3 -2
  42. package/dist/esm/useParams.js.map +1 -1
  43. package/dist/llms/rules/guide.d.ts +1 -1
  44. package/dist/llms/rules/guide.js +14 -7
  45. package/package.json +3 -3
  46. package/src/HeadContent.tsx +8 -6
  47. package/src/ScriptOnce.tsx +1 -1
  48. package/src/fileRoute.ts +10 -2
  49. package/src/index.tsx +10 -5
  50. package/src/link.tsx +66 -79
  51. package/src/route.tsx +74 -17
  52. package/src/scroll-restoration.tsx +11 -4
  53. package/src/ssr/serializer.ts +7 -0
  54. package/src/useBlocker.tsx +1 -1
  55. package/src/useNavigate.tsx +4 -18
  56. package/src/useParams.tsx +5 -3
@@ -2256,7 +2256,7 @@ Literally any library that **can return a promise and read/write data** can be i
2256
2256
 
2257
2257
  ## Using Loaders to ensure data is loaded
2258
2258
 
2259
- The easiest way to use integrate and external caching/data library into Router is to use \`route.loader\`s to ensure that the data required inside of a route has been loaded and is ready to be displayed.
2259
+ The easiest way to integrate external caching/data library into Router is to use \`route.loader\`s to ensure that the data required inside of a route has been loaded and is ready to be displayed.
2260
2260
 
2261
2261
  > ⚠️ BUT WHY? It's very important to preload your critical render data in the loader for a few reasons:
2262
2262
  >
@@ -2324,7 +2324,7 @@ export const Route = createFileRoute('/posts')({
2324
2324
 
2325
2325
  ### Error handling with TanStack Query
2326
2326
 
2327
- When an error occurs while using \`suspense\` with \`TanStack Query\`, you'll need to let queries know that you want to try again when re-rendering. This can be done by using the \`reset\` function provided by the \`useQueryErrorResetBoundary\` hook. We can invoke this function in an effect as soon as the error component mounts. This will make sure that the query is reset and will try to fetch data again when the route component is rendered again. This will also cover cases where users navigate away from our route instead of clicking the \`retry\` button.
2327
+ When an error occurs while using \`suspense\` with \`TanStack Query\`, you need to let queries know that you want to try again when re-rendering. This can be done by using the \`reset\` function provided by the \`useQueryErrorResetBoundary\` hook. You can invoke this function in an effect as soon as the error component mounts. This will make sure that the query is reset and will try to fetch data again when the route component is rendered again. This will also cover cases where users navigate away from the route instead of clicking the \`retry\` button.
2328
2328
 
2329
2329
  \`\`\`tsx
2330
2330
  export const Route = createFileRoute('/')({
@@ -2866,7 +2866,7 @@ type ToOptions<
2866
2866
  TTo extends string = '',
2867
2867
  > = {
2868
2868
  // \`from\` is an optional route ID or path. If it is not supplied, only absolute paths will be auto-completed and type-safe. It's common to supply the route.fullPath of the origin route you are rendering from for convenience. If you don't know the origin route, leave this empty and work with absolute paths or unsafe relative paths.
2869
- from: string
2869
+ from?: string
2870
2870
  // \`to\` can be an absolute route path or a relative path from the \`from\` option to a valid route path. ⚠️ Do not interpolate path params, hash or search params into the \`to\` options. Use the \`params\`, \`search\`, and \`hash\` options instead.
2871
2871
  to: string
2872
2872
  // \`params\` is either an object of path params to interpolate into the \`to\` option or a function that supplies the previous params and allows you to return new ones. This is the only way to interpolate dynamic parameters into the final URL. Depending on the \`from\` and \`to\` route, you may need to supply none, some or all of the path params. TypeScript will notify you of the required params if there are any.
@@ -3021,7 +3021,7 @@ Keep in mind that normally dynamic segment params are \`string\` values, but the
3021
3021
 
3022
3022
  By default, all links are absolute unless a \`from\` route path is provided. This means that the above link will always navigate to the \`/about\` route regardless of what route you are currently on.
3023
3023
 
3024
- If you want to make a link that is relative to the current route, you can provide a \`from\` route path:
3024
+ Relative links can be combined with a \`from\` route path. If a from route path isn't provided, relative paths default to the current active location.
3025
3025
 
3026
3026
  \`\`\`tsx
3027
3027
  const postIdRoute = createRoute({
@@ -3039,9 +3039,9 @@ As seen above, it's common to provide the \`route.fullPath\` as the \`from\` rou
3039
3039
 
3040
3040
  ### Special relative paths: \`"."\` and \`".."\`
3041
3041
 
3042
- Quite often you might want to reload the current location, for example, to rerun the loaders on the current and/or parent routes, or maybe there was a change in search parameters. This can be achieved by specifying a \`to\` route path of \`"."\` which will reload the current location. This is only applicable to the current location, and hence any \`from\` route path specified is ignored.
3042
+ Quite often you might want to reload the current location or another \`from\` path, for example, to rerun the loaders on the current and/or parent routes, or maybe navigate back to a parent route. This can be achieved by specifying a \`to\` route path of \`"."\` which will reload the current location or provided \`from\` path.
3043
3043
 
3044
- Another common need is to navigate one route back relative to the current location or some other matched route in the current tree. By specifying a \`to\` route path of \`".."\` navigation will be resolved to either the first parent route preceding the current location or, if specified, preceding the \`"from"\` route path.
3044
+ Another common need is to navigate one route back relative to the current location or another path. By specifying a \`to\` route path of \`".."\` navigation will be resolved to the first parent route preceding the current location.
3045
3045
 
3046
3046
  \`\`\`tsx
3047
3047
  export const Route = createFileRoute('/posts/$postId')({
@@ -3052,7 +3052,14 @@ function PostComponent() {
3052
3052
  return (
3053
3053
  <div>
3054
3054
  <Link to=".">Reload the current route of /posts/$postId</Link>
3055
- <Link to="..">Navigate to /posts</Link>
3055
+ <Link to="..">Navigate back to /posts</Link>
3056
+ // the below are all equivalent
3057
+ <Link to="/posts">Navigate back to /posts</Link>
3058
+ <Link from="/posts" to=".">
3059
+ Navigate back to /posts
3060
+ </Link>
3061
+ // the below are all equivalent
3062
+ <Link to="/">Navigate to root</Link>
3056
3063
  <Link from="/posts" to="..">
3057
3064
  Navigate to root
3058
3065
  </Link>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.132.0-alpha.2",
3
+ "version": "1.132.0-alpha.21",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -79,8 +79,8 @@
79
79
  "isbot": "^5.1.22",
80
80
  "tiny-invariant": "^1.3.3",
81
81
  "tiny-warning": "^1.0.3",
82
- "@tanstack/history": "1.132.0-alpha.1",
83
- "@tanstack/router-core": "1.132.0-alpha.2"
82
+ "@tanstack/router-core": "1.132.0-alpha.21",
83
+ "@tanstack/history": "1.132.0-alpha.1"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@testing-library/jest-dom": "^6.6.3",
@@ -17,9 +17,11 @@ export const useTags = () => {
17
17
  const resultMeta: Array<RouterManagedTag> = []
18
18
  const metaByAttribute: Record<string, true> = {}
19
19
  let title: RouterManagedTag | undefined
20
- ;[...routeMeta].reverse().forEach((metas) => {
21
- ;[...metas].reverse().forEach((m) => {
22
- if (!m) return
20
+ for (let i = routeMeta.length - 1; i >= 0; i--) {
21
+ const metas = routeMeta[i]!
22
+ for (let j = metas.length - 1; j >= 0; j--) {
23
+ const m = metas[j]
24
+ if (!m) continue
23
25
 
24
26
  if (m.title) {
25
27
  if (!title) {
@@ -32,7 +34,7 @@ export const useTags = () => {
32
34
  const attribute = m.name ?? m.property
33
35
  if (attribute) {
34
36
  if (metaByAttribute[attribute]) {
35
- return
37
+ continue
36
38
  } else {
37
39
  metaByAttribute[attribute] = true
38
40
  }
@@ -45,8 +47,8 @@ export const useTags = () => {
45
47
  },
46
48
  })
47
49
  }
48
- })
49
- })
50
+ }
51
+ }
50
52
 
51
53
  if (title) {
52
54
  resultMeta.push(title)
@@ -13,7 +13,7 @@ export function ScriptOnce({
13
13
  <script
14
14
  className="$tsr"
15
15
  dangerouslySetInnerHTML={{
16
- __html: [children].filter(Boolean).join('\n'),
16
+ __html: [children].filter(Boolean).join('\n') + ';$_TSR.c()',
17
17
  }}
18
18
  />
19
19
  )
package/src/fileRoute.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  FileBaseRouteOptions,
21
21
  FileRoutesByPath,
22
22
  LazyRouteOptions,
23
+ Register,
23
24
  RegisteredRouter,
24
25
  ResolveParams,
25
26
  Route,
@@ -76,6 +77,7 @@ export class FileRoute<
76
77
  }
77
78
 
78
79
  createRoute = <
80
+ TRegister extends Register = Register,
79
81
  TSearchValidator = undefined,
80
82
  TParams = ResolveParams<TPath>,
81
83
  TRouteContextFn = AnyContext,
@@ -83,8 +85,10 @@ export class FileRoute<
83
85
  TLoaderDeps extends Record<string, any> = {},
84
86
  TLoaderFn = undefined,
85
87
  TChildren = unknown,
88
+ TSSR = unknown,
86
89
  >(
87
90
  options?: FileBaseRouteOptions<
91
+ TRegister,
88
92
  TParentRoute,
89
93
  TId,
90
94
  TPath,
@@ -94,7 +98,9 @@ export class FileRoute<
94
98
  TLoaderFn,
95
99
  AnyContext,
96
100
  TRouteContextFn,
97
- TBeforeLoadFn
101
+ TBeforeLoadFn,
102
+ AnyContext,
103
+ TSSR
98
104
  > &
99
105
  UpdatableRouteOptions<
100
106
  TParentRoute,
@@ -109,6 +115,7 @@ export class FileRoute<
109
115
  TBeforeLoadFn
110
116
  >,
111
117
  ): Route<
118
+ TRegister,
112
119
  TParentRoute,
113
120
  TPath,
114
121
  TFullPath,
@@ -122,7 +129,8 @@ export class FileRoute<
122
129
  TLoaderDeps,
123
130
  TLoaderFn,
124
131
  TChildren,
125
- unknown
132
+ unknown,
133
+ TSSR
126
134
  > => {
127
135
  warning(
128
136
  this.silent,
package/src/index.tsx CHANGED
@@ -11,17 +11,13 @@ export {
11
11
  parsePathname,
12
12
  interpolatePath,
13
13
  matchPathname,
14
- removeBasepath,
15
14
  matchByPath,
16
- encode,
17
- decode,
18
15
  rootRouteId,
19
16
  defaultSerializeError,
20
17
  defaultParseSearch,
21
18
  defaultStringifySearch,
22
19
  parseSearchWith,
23
20
  stringifySearchWith,
24
- pick,
25
21
  functionalUpdate,
26
22
  replaceEqualDeep,
27
23
  isPlainObject,
@@ -30,6 +26,7 @@ export {
30
26
  createControlledPromise,
31
27
  retainSearchParams,
32
28
  stripSearchParams,
29
+ createSerializationAdapter,
33
30
  } from '@tanstack/router-core'
34
31
 
35
32
  export type {
@@ -110,6 +107,8 @@ export type {
110
107
  RouteById,
111
108
  RootRouteOptions,
112
109
  CreateFileRoute,
110
+ SerializationAdapter,
111
+ AnySerializationAdapter,
113
112
  } from '@tanstack/router-core'
114
113
 
115
114
  export {
@@ -248,7 +247,7 @@ export { useMatch } from './useMatch'
248
247
  export { useLoaderDeps } from './useLoaderDeps'
249
248
  export { useLoaderData } from './useLoaderData'
250
249
 
251
- export { redirect, isRedirect } from '@tanstack/router-core'
250
+ export { redirect, isRedirect, createRouterConfig } from '@tanstack/router-core'
252
251
 
253
252
  export {
254
253
  RouteApi,
@@ -350,3 +349,9 @@ export { ScriptOnce } from './ScriptOnce'
350
349
  export { Asset } from './Asset'
351
350
  export { HeadContent } from './HeadContent'
352
351
  export { Scripts } from './Scripts'
352
+ export type * from './ssr/serializer'
353
+ export { rewriteBasepath, composeRewrites } from '@tanstack/router-core'
354
+ export type {
355
+ LocationRewrite,
356
+ LocationRewriteFunction,
357
+ } from '@tanstack/router-core'
package/src/link.tsx CHANGED
@@ -12,7 +12,6 @@ import { useRouter } from './useRouter'
12
12
 
13
13
  import { useForwardedRef, useIntersectionObserver } from './utils'
14
14
 
15
- import { useMatch } from './useMatch'
16
15
  import type {
17
16
  AnyRouter,
18
17
  Constrain,
@@ -79,39 +78,24 @@ export function useLinkProps<
79
78
  ...propsSafeToSpread
80
79
  } = options
81
80
 
82
- // If this link simply reloads the current route,
83
- // make sure it has a new key so it will trigger a data refresh
84
-
85
- // If this `to` is a valid external URL, return
86
- // null for LinkUtils
87
-
88
- const type: 'internal' | 'external' = React.useMemo(() => {
89
- try {
90
- new URL(to as any)
91
- return 'external'
92
- } catch {}
93
- return 'internal'
94
- }, [to])
95
-
96
81
  // subscribe to search params to re-build location if it changes
97
82
  const currentSearch = useRouterState({
98
83
  select: (s) => s.location.search,
99
84
  structuralSharing: true as any,
100
85
  })
101
86
 
102
- const from = useMatch({
103
- strict: false,
104
- select: (match) => options.from ?? match.fullPath,
105
- })
87
+ const from = options.from
106
88
 
107
- const next = React.useMemo(
108
- () => router.buildLocation({ ...options, from } as any),
89
+ const _options = React.useMemo(
90
+ () => {
91
+ return { ...options, from }
92
+ },
109
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
110
94
  [
111
95
  router,
112
96
  currentSearch,
113
- options._fromLocation,
114
97
  from,
98
+ options._fromLocation,
115
99
  options.hash,
116
100
  options.to,
117
101
  options.search,
@@ -122,10 +106,41 @@ export function useLinkProps<
122
106
  ],
123
107
  )
124
108
 
125
- const isExternal = type === 'external'
109
+ const next = React.useMemo(
110
+ () => router.buildLocation({ ..._options } as any),
111
+ [router, _options],
112
+ )
113
+
114
+ const hrefOption = React.useMemo(() => {
115
+ if (disabled) {
116
+ return undefined
117
+ }
118
+ let href = next.maskedLocation ? next.maskedLocation.url : next.url
119
+
120
+ let external = false
121
+ if (router.origin) {
122
+ if (href.startsWith(router.origin)) {
123
+ href = href.replace(router.origin, '') || '/'
124
+ } else {
125
+ external = true
126
+ }
127
+ }
128
+ return { href, external }
129
+ }, [disabled, next.maskedLocation, next.url, router.origin])
130
+
131
+ const externalLink = React.useMemo(() => {
132
+ if (hrefOption?.external) {
133
+ return hrefOption.href
134
+ }
135
+ try {
136
+ new URL(to as any)
137
+ return to
138
+ } catch {}
139
+ return undefined
140
+ }, [to, hrefOption])
126
141
 
127
142
  const preload =
128
- options.reloadDocument || isExternal
143
+ options.reloadDocument || externalLink
129
144
  ? false
130
145
  : (userPreload ?? router.options.defaultPreload)
131
146
  const preloadDelay =
@@ -133,7 +148,7 @@ export function useLinkProps<
133
148
 
134
149
  const isActive = useRouterState({
135
150
  select: (s) => {
136
- if (isExternal) return false
151
+ if (externalLink) return false
137
152
  if (activeOptions?.exact) {
138
153
  const testExact = exactPathTest(
139
154
  s.location.pathname,
@@ -180,34 +195,12 @@ export function useLinkProps<
180
195
  },
181
196
  })
182
197
 
183
- const doPreload = React.useCallback(
184
- () => {
185
- router.preloadRoute({ ...options, from } as any).catch((err) => {
186
- console.warn(err)
187
- console.warn(preloadWarning)
188
- })
189
- },
190
- // eslint-disable-next-line react-hooks/exhaustive-deps
191
- [
192
- router,
193
- options.to,
194
- options._fromLocation,
195
- from,
196
- options.search,
197
- options.hash,
198
- options.params,
199
- options.state,
200
- options.mask,
201
- options.unsafeRelative,
202
- options.hashScrollIntoView,
203
- options.href,
204
- options.ignoreBlocker,
205
- options.reloadDocument,
206
- options.replace,
207
- options.resetScroll,
208
- options.viewTransition,
209
- ],
210
- )
198
+ const doPreload = React.useCallback(() => {
199
+ router.preloadRoute({ ..._options } as any).catch((err) => {
200
+ console.warn(err)
201
+ console.warn(preloadWarning)
202
+ })
203
+ }, [router, _options])
211
204
 
212
205
  const preloadViewportIoCallback = React.useCallback(
213
206
  (entry: IntersectionObserverEntry | undefined) => {
@@ -235,25 +228,6 @@ export function useLinkProps<
235
228
  }
236
229
  }, [disabled, doPreload, preload])
237
230
 
238
- if (isExternal) {
239
- return {
240
- ...propsSafeToSpread,
241
- ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
242
- type,
243
- href: to,
244
- ...(children && { children }),
245
- ...(target && { target }),
246
- ...(disabled && { disabled }),
247
- ...(style && { style }),
248
- ...(className && { className }),
249
- ...(onClick && { onClick }),
250
- ...(onFocus && { onFocus }),
251
- ...(onMouseEnter && { onMouseEnter }),
252
- ...(onMouseLeave && { onMouseLeave }),
253
- ...(onTouchStart && { onTouchStart }),
254
- }
255
- }
256
-
257
231
  // The click handler
258
232
  const handleClick = (e: React.MouseEvent) => {
259
233
  if (
@@ -277,8 +251,7 @@ export function useLinkProps<
277
251
  // All is well? Navigate!
278
252
  // N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
279
253
  router.navigate({
280
- ...options,
281
- from,
254
+ ..._options,
282
255
  replace,
283
256
  resetScroll,
284
257
  hashScrollIntoView,
@@ -289,6 +262,24 @@ export function useLinkProps<
289
262
  }
290
263
  }
291
264
 
265
+ if (externalLink) {
266
+ return {
267
+ ...propsSafeToSpread,
268
+ ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
269
+ href: externalLink,
270
+ ...(children && { children }),
271
+ ...(target && { target }),
272
+ ...(disabled && { disabled }),
273
+ ...(style && { style }),
274
+ ...(className && { className }),
275
+ ...(onClick && { onClick }),
276
+ ...(onFocus && { onFocus }),
277
+ ...(onMouseEnter && { onMouseEnter }),
278
+ ...(onMouseLeave && { onMouseLeave }),
279
+ ...(onTouchStart && { onTouchStart }),
280
+ }
281
+ }
282
+
292
283
  // The click handler
293
284
  const handleFocus = (_: React.MouseEvent) => {
294
285
  if (disabled) return
@@ -358,11 +349,7 @@ export function useLinkProps<
358
349
  ...propsSafeToSpread,
359
350
  ...resolvedActiveProps,
360
351
  ...resolvedInactiveProps,
361
- href: disabled
362
- ? undefined
363
- : next.maskedLocation
364
- ? router.history.createHref(next.maskedLocation.href)
365
- : router.history.createHref(next.href),
352
+ href: hrefOption?.href,
366
353
  ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
367
354
  onClick: composeHandlers([onClick, handleClick]),
368
355
  onFocus: composeHandlers([onFocus, handleFocus]),