@tanstack/react-router 1.131.27 → 1.131.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/link.cjs +41 -55
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/useActiveLocation.cjs +39 -0
- package/dist/cjs/useActiveLocation.cjs.map +1 -0
- package/dist/cjs/useActiveLocation.d.cts +7 -0
- package/dist/cjs/useNavigate.cjs +6 -9
- package/dist/cjs/useNavigate.cjs.map +1 -1
- package/dist/esm/link.js +41 -55
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/useActiveLocation.d.ts +7 -0
- package/dist/esm/useActiveLocation.js +39 -0
- package/dist/esm/useActiveLocation.js.map +1 -0
- package/dist/esm/useNavigate.js +6 -9
- package/dist/esm/useNavigate.js.map +1 -1
- package/dist/llms/rules/guide.d.ts +1 -1
- package/dist/llms/rules/guide.js +14 -7
- package/package.json +2 -2
- package/src/link.tsx +46 -56
- package/src/useActiveLocation.ts +57 -0
- package/src/useNavigate.tsx +6 -14
package/dist/llms/rules/guide.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.131.
|
|
3
|
+
"version": "1.131.30",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"tiny-invariant": "^1.3.3",
|
|
81
81
|
"tiny-warning": "^1.0.3",
|
|
82
82
|
"@tanstack/history": "1.131.2",
|
|
83
|
-
"@tanstack/router-core": "1.131.
|
|
83
|
+
"@tanstack/router-core": "1.131.30"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@testing-library/jest-dom": "^6.6.3",
|
package/src/link.tsx
CHANGED
|
@@ -7,12 +7,12 @@ import {
|
|
|
7
7
|
preloadWarning,
|
|
8
8
|
removeTrailingSlash,
|
|
9
9
|
} from '@tanstack/router-core'
|
|
10
|
+
import { useActiveLocation } from './useActiveLocation'
|
|
10
11
|
import { useRouterState } from './useRouterState'
|
|
11
12
|
import { useRouter } from './useRouter'
|
|
12
13
|
|
|
13
14
|
import { useForwardedRef, useIntersectionObserver } from './utils'
|
|
14
15
|
|
|
15
|
-
import { useMatch } from './useMatch'
|
|
16
16
|
import type {
|
|
17
17
|
AnyRouter,
|
|
18
18
|
Constrain,
|
|
@@ -99,19 +99,27 @@ export function useLinkProps<
|
|
|
99
99
|
structuralSharing: true as any,
|
|
100
100
|
})
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
select: (
|
|
102
|
+
// subscribe to location here to re-build fromPath if it changes
|
|
103
|
+
const routerLocation = useRouterState({
|
|
104
|
+
select: (s) => s.location,
|
|
105
|
+
structuralSharing: true as any,
|
|
105
106
|
})
|
|
106
107
|
|
|
107
|
-
const
|
|
108
|
-
|
|
108
|
+
const { getFromPath } = useActiveLocation()
|
|
109
|
+
|
|
110
|
+
const from = getFromPath(options.from)
|
|
111
|
+
|
|
112
|
+
const _options = React.useMemo(
|
|
113
|
+
() => {
|
|
114
|
+
return { ...options, from }
|
|
115
|
+
},
|
|
109
116
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
110
117
|
[
|
|
111
118
|
router,
|
|
119
|
+
routerLocation,
|
|
112
120
|
currentSearch,
|
|
113
|
-
options._fromLocation,
|
|
114
121
|
from,
|
|
122
|
+
options._fromLocation,
|
|
115
123
|
options.hash,
|
|
116
124
|
options.to,
|
|
117
125
|
options.search,
|
|
@@ -122,6 +130,11 @@ export function useLinkProps<
|
|
|
122
130
|
],
|
|
123
131
|
)
|
|
124
132
|
|
|
133
|
+
const next = React.useMemo(
|
|
134
|
+
() => router.buildLocation({ ..._options } as any),
|
|
135
|
+
[router, _options],
|
|
136
|
+
)
|
|
137
|
+
|
|
125
138
|
const isExternal = type === 'external'
|
|
126
139
|
|
|
127
140
|
const preload =
|
|
@@ -180,34 +193,12 @@ export function useLinkProps<
|
|
|
180
193
|
},
|
|
181
194
|
})
|
|
182
195
|
|
|
183
|
-
const doPreload = React.useCallback(
|
|
184
|
-
() => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
)
|
|
196
|
+
const doPreload = React.useCallback(() => {
|
|
197
|
+
router.preloadRoute({ ..._options } as any).catch((err) => {
|
|
198
|
+
console.warn(err)
|
|
199
|
+
console.warn(preloadWarning)
|
|
200
|
+
})
|
|
201
|
+
}, [router, _options])
|
|
211
202
|
|
|
212
203
|
const preloadViewportIoCallback = React.useCallback(
|
|
213
204
|
(entry: IntersectionObserverEntry | undefined) => {
|
|
@@ -235,25 +226,6 @@ export function useLinkProps<
|
|
|
235
226
|
}
|
|
236
227
|
}, [disabled, doPreload, preload])
|
|
237
228
|
|
|
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
229
|
// The click handler
|
|
258
230
|
const handleClick = (e: React.MouseEvent) => {
|
|
259
231
|
if (
|
|
@@ -277,8 +249,7 @@ export function useLinkProps<
|
|
|
277
249
|
// All is well? Navigate!
|
|
278
250
|
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
|
|
279
251
|
router.navigate({
|
|
280
|
-
...
|
|
281
|
-
from,
|
|
252
|
+
..._options,
|
|
282
253
|
replace,
|
|
283
254
|
resetScroll,
|
|
284
255
|
hashScrollIntoView,
|
|
@@ -289,6 +260,25 @@ export function useLinkProps<
|
|
|
289
260
|
}
|
|
290
261
|
}
|
|
291
262
|
|
|
263
|
+
if (isExternal) {
|
|
264
|
+
return {
|
|
265
|
+
...propsSafeToSpread,
|
|
266
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
267
|
+
type,
|
|
268
|
+
href: to,
|
|
269
|
+
...(children && { children }),
|
|
270
|
+
...(target && { target }),
|
|
271
|
+
...(disabled && { disabled }),
|
|
272
|
+
...(style && { style }),
|
|
273
|
+
...(className && { className }),
|
|
274
|
+
...(onClick && { onClick }),
|
|
275
|
+
...(onFocus && { onFocus }),
|
|
276
|
+
...(onMouseEnter && { onMouseEnter }),
|
|
277
|
+
...(onMouseLeave && { onMouseLeave }),
|
|
278
|
+
...(onTouchStart && { onTouchStart }),
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
292
282
|
// The click handler
|
|
293
283
|
const handleFocus = (_: React.MouseEvent) => {
|
|
294
284
|
if (disabled) return
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { last } from '@tanstack/router-core'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import { useRouter } from './useRouter'
|
|
4
|
+
import { useMatch } from './useMatch'
|
|
5
|
+
import { useRouterState } from './useRouterState'
|
|
6
|
+
import type { ParsedLocation } from '@tanstack/router-core'
|
|
7
|
+
|
|
8
|
+
export type UseActiveLocationResult = {
|
|
9
|
+
activeLocation: ParsedLocation
|
|
10
|
+
getFromPath: (from?: string) => string
|
|
11
|
+
setActiveLocation: (location?: ParsedLocation) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useActiveLocation = (
|
|
15
|
+
location?: ParsedLocation,
|
|
16
|
+
): UseActiveLocationResult => {
|
|
17
|
+
const router = useRouter()
|
|
18
|
+
const routerLocation = useRouterState({ select: (state) => state.location })
|
|
19
|
+
const [activeLocation, setActiveLocation] = useState<ParsedLocation>(
|
|
20
|
+
location ?? routerLocation,
|
|
21
|
+
)
|
|
22
|
+
const [customActiveLocation, setCustomActiveLocation] = useState<
|
|
23
|
+
ParsedLocation | undefined
|
|
24
|
+
>(location)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setActiveLocation(customActiveLocation ?? routerLocation)
|
|
28
|
+
}, [routerLocation, customActiveLocation])
|
|
29
|
+
|
|
30
|
+
const matchIndex = useMatch({
|
|
31
|
+
strict: false,
|
|
32
|
+
select: (match) => match.index,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const getFromPath = useCallback(
|
|
36
|
+
(from?: string) => {
|
|
37
|
+
const activeLocationMatches = router.matchRoutes(activeLocation, {
|
|
38
|
+
_buildLocation: false,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const activeLocationMatch = last(activeLocationMatches)
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
from ??
|
|
45
|
+
activeLocationMatch?.fullPath ??
|
|
46
|
+
router.state.matches[matchIndex]!.fullPath
|
|
47
|
+
)
|
|
48
|
+
},
|
|
49
|
+
[activeLocation, matchIndex, router],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
activeLocation,
|
|
54
|
+
getFromPath,
|
|
55
|
+
setActiveLocation: setCustomActiveLocation,
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/useNavigate.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import { useRouter } from './useRouter'
|
|
3
|
-
import {
|
|
3
|
+
import { useActiveLocation } from './useActiveLocation'
|
|
4
4
|
import type {
|
|
5
5
|
AnyRouter,
|
|
6
6
|
FromPathOption,
|
|
@@ -15,29 +15,21 @@ export function useNavigate<
|
|
|
15
15
|
>(_defaultOpts?: {
|
|
16
16
|
from?: FromPathOption<TRouter, TDefaultFrom>
|
|
17
17
|
}): UseNavigateResult<TDefaultFrom> {
|
|
18
|
-
const
|
|
18
|
+
const router = useRouter()
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
// as much as possible
|
|
22
|
-
const matchIndex = useMatch({
|
|
23
|
-
strict: false,
|
|
24
|
-
select: (match) => match.index,
|
|
25
|
-
})
|
|
20
|
+
const { getFromPath, activeLocation } = useActiveLocation()
|
|
26
21
|
|
|
27
22
|
return React.useCallback(
|
|
28
23
|
(options: NavigateOptions) => {
|
|
29
|
-
const from =
|
|
30
|
-
options.from ??
|
|
31
|
-
_defaultOpts?.from ??
|
|
32
|
-
state.matches[matchIndex]!.fullPath
|
|
24
|
+
const from = getFromPath(options.from ?? _defaultOpts?.from)
|
|
33
25
|
|
|
34
|
-
return navigate({
|
|
26
|
+
return router.navigate({
|
|
35
27
|
...options,
|
|
36
28
|
from,
|
|
37
29
|
})
|
|
38
30
|
},
|
|
39
31
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
-
[_defaultOpts?.from,
|
|
32
|
+
[_defaultOpts?.from, router, getFromPath, activeLocation],
|
|
41
33
|
) as UseNavigateResult<TDefaultFrom>
|
|
42
34
|
}
|
|
43
35
|
|