@depup/tanstack__react-router 1.167.5-depup.0 → 1.168.2-depup.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.
- package/README.md +3 -4
- package/changes.json +2 -6
- package/dist/cjs/Match.cjs +147 -58
- package/dist/cjs/Match.cjs.map +1 -1
- package/dist/cjs/Matches.cjs +22 -24
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Scripts.cjs +36 -32
- package/dist/cjs/Scripts.cjs.map +1 -1
- package/dist/cjs/Transitioner.cjs +10 -16
- package/dist/cjs/Transitioner.cjs.map +1 -1
- package/dist/cjs/fileRoute.cjs +4 -6
- package/dist/cjs/fileRoute.cjs.map +1 -1
- package/dist/cjs/headContentUtils.cjs +147 -59
- package/dist/cjs/headContentUtils.cjs.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.dev.cjs +1 -1
- package/dist/cjs/link.cjs +34 -29
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/not-found.cjs +20 -2
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/renderRouteNotFound.cjs +3 -3
- package/dist/cjs/renderRouteNotFound.cjs.map +1 -1
- package/dist/cjs/route.cjs +0 -2
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs +2 -1
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/routerStores.cjs +21 -0
- package/dist/cjs/routerStores.cjs.map +1 -0
- package/dist/cjs/routerStores.d.cts +7 -0
- package/dist/cjs/ssr/RouterClient.cjs +1 -1
- package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
- package/dist/cjs/ssr/renderRouterToStream.cjs +2 -2
- package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
- package/dist/cjs/ssr/renderRouterToString.cjs +1 -1
- package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
- package/dist/cjs/useCanGoBack.cjs +7 -2
- package/dist/cjs/useCanGoBack.cjs.map +1 -1
- package/dist/cjs/useLocation.cjs +21 -2
- package/dist/cjs/useLocation.cjs.map +1 -1
- package/dist/cjs/useMatch.cjs +35 -11
- package/dist/cjs/useMatch.cjs.map +1 -1
- package/dist/cjs/useRouter.cjs +3 -3
- package/dist/cjs/useRouter.cjs.map +1 -1
- package/dist/cjs/useRouterState.cjs +2 -2
- package/dist/cjs/useRouterState.cjs.map +1 -1
- package/dist/esm/Match.js +148 -57
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.js +23 -24
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/Scripts.js +36 -32
- package/dist/esm/Scripts.js.map +1 -1
- package/dist/esm/Transitioner.js +10 -16
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/fileRoute.js +4 -4
- package/dist/esm/fileRoute.js.map +1 -1
- package/dist/esm/headContentUtils.js +148 -60
- package/dist/esm/headContentUtils.js.map +1 -1
- package/dist/esm/index.dev.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/link.js +34 -29
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/not-found.js +20 -2
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/renderRouteNotFound.js +3 -2
- package/dist/esm/renderRouteNotFound.js.map +1 -1
- package/dist/esm/route.js +0 -2
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.js +2 -1
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/routerStores.d.ts +7 -0
- package/dist/esm/routerStores.js +20 -0
- package/dist/esm/routerStores.js.map +1 -0
- package/dist/esm/ssr/RouterClient.js +1 -1
- package/dist/esm/ssr/RouterClient.js.map +1 -1
- package/dist/esm/ssr/renderRouterToStream.js +2 -2
- package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
- package/dist/esm/ssr/renderRouterToString.js +1 -1
- package/dist/esm/ssr/renderRouterToString.js.map +1 -1
- package/dist/esm/useCanGoBack.js +6 -2
- package/dist/esm/useCanGoBack.js.map +1 -1
- package/dist/esm/useLocation.js +20 -2
- package/dist/esm/useLocation.js.map +1 -1
- package/dist/esm/useMatch.js +35 -10
- package/dist/esm/useMatch.js.map +1 -1
- package/dist/esm/useRouter.js +3 -2
- package/dist/esm/useRouter.js.map +1 -1
- package/dist/esm/useRouterState.js +2 -2
- package/dist/esm/useRouterState.js.map +1 -1
- package/dist/llms/rules/api.d.ts +1 -1
- package/dist/llms/rules/api.js +13 -19
- package/dist/llms/rules/guide.d.ts +1 -1
- package/dist/llms/rules/guide.js +27 -6
- package/package.json +5 -11
- package/src/Match.tsx +274 -81
- package/src/Matches.tsx +48 -30
- package/src/Scripts.tsx +72 -44
- package/src/Transitioner.tsx +24 -16
- package/src/fileRoute.ts +7 -9
- package/src/headContentUtils.tsx +210 -27
- package/src/link.tsx +66 -71
- package/src/not-found.tsx +41 -4
- package/src/renderRouteNotFound.tsx +6 -6
- package/src/route.tsx +0 -2
- package/src/router.ts +2 -1
- package/src/routerStores.ts +26 -0
- package/src/ssr/RouterClient.tsx +1 -1
- package/src/ssr/renderRouterToStream.tsx +2 -2
- package/src/ssr/renderRouterToString.tsx +1 -1
- package/src/useCanGoBack.ts +14 -2
- package/src/useLocation.tsx +32 -5
- package/src/useMatch.tsx +68 -19
- package/src/useRouter.tsx +7 -5
- package/src/useRouterState.tsx +4 -2
package/dist/llms/rules/guide.js
CHANGED
|
@@ -8384,7 +8384,9 @@ For validation libraries we recommend using adapters which infer the correct \`i
|
|
|
8384
8384
|
|
|
8385
8385
|
### Zod
|
|
8386
8386
|
|
|
8387
|
-
An adapter is provided for [Zod](https://zod.dev/) which will pipe through the correct \`input\` type and \`output\` type
|
|
8387
|
+
An adapter is provided for [Zod](https://zod.dev/) which will pipe through the correct \`input\` type and \`output\` type.
|
|
8388
|
+
|
|
8389
|
+
For Zod v3:
|
|
8388
8390
|
|
|
8389
8391
|
\`\`\`tsx
|
|
8390
8392
|
import { zodValidator } from '@tanstack/zod-adapter'
|
|
@@ -8401,13 +8403,30 @@ export const Route = createFileRoute('/shop/products/')({
|
|
|
8401
8403
|
})
|
|
8402
8404
|
\`\`\`
|
|
8403
8405
|
|
|
8404
|
-
|
|
8406
|
+
With Zod v4, you should directly use the schema in \`validateSearch\`:
|
|
8407
|
+
|
|
8408
|
+
\`\`\`tsx
|
|
8409
|
+
import { z } from 'zod'
|
|
8410
|
+
|
|
8411
|
+
const productSearchSchema = z.object({
|
|
8412
|
+
page: z.number().default(1),
|
|
8413
|
+
filter: z.string().default(''),
|
|
8414
|
+
sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
|
|
8415
|
+
})
|
|
8416
|
+
|
|
8417
|
+
export const Route = createFileRoute('/shop/products/')({
|
|
8418
|
+
// With Zod v4, we can use the schema without the adapter
|
|
8419
|
+
validateSearch: productSearchSchema,
|
|
8420
|
+
})
|
|
8421
|
+
\`\`\`
|
|
8422
|
+
|
|
8423
|
+
The important part here is the following use of \`Link\` no longer requires \`search\` params:
|
|
8405
8424
|
|
|
8406
8425
|
\`\`\`tsx
|
|
8407
8426
|
<Link to="/shop/products" />
|
|
8408
8427
|
\`\`\`
|
|
8409
8428
|
|
|
8410
|
-
|
|
8429
|
+
In Zod v3, the use of \`catch\` here overrides the types and makes \`page\`, \`filter\` and \`sort\` \`unknown\` causing type loss. We have handled this case by providing a \`fallback\` generic function which retains the types but provides a \`fallback\` value when validation fails:
|
|
8411
8430
|
|
|
8412
8431
|
\`\`\`tsx
|
|
8413
8432
|
import { fallback, zodValidator } from '@tanstack/zod-adapter'
|
|
@@ -8428,7 +8447,9 @@ export const Route = createFileRoute('/shop/products/')({
|
|
|
8428
8447
|
|
|
8429
8448
|
Therefore when navigating to this route, \`search\` is optional and retains the correct types.
|
|
8430
8449
|
|
|
8431
|
-
|
|
8450
|
+
In Zod v4, schemas may use \`catch\` instead of the fallback and will retain type inference throughout.
|
|
8451
|
+
|
|
8452
|
+
While not recommended, it is also possible to configure \`input\` and \`output\` type in case the \`output\` type is more accurate than the \`input\` type:
|
|
8432
8453
|
|
|
8433
8454
|
\`\`\`tsx
|
|
8434
8455
|
const productSearchSchema = z.object({
|
|
@@ -8645,7 +8666,7 @@ Now that you've learned how to read your route's search params, you'll be happy
|
|
|
8645
8666
|
|
|
8646
8667
|
The best way to update search params is to use the \`search\` prop on the \`<Link />\` component.
|
|
8647
8668
|
|
|
8648
|
-
If the search for the current page shall be updated and the \`from\` prop is specified, the \`to\` prop can be omitted.
|
|
8669
|
+
If the search for the current page shall be updated and the \`from\` prop is specified, the \`to\` prop can be omitted.
|
|
8649
8670
|
Here's an example:
|
|
8650
8671
|
|
|
8651
8672
|
\`\`\`tsx title="src/routes/shop/products.tsx"
|
|
@@ -8666,7 +8687,7 @@ const ProductList = () => {
|
|
|
8666
8687
|
|
|
8667
8688
|
If you want to update the search params in a generic component that is rendered on multiple routes, specifying \`from\` can be challenging.
|
|
8668
8689
|
|
|
8669
|
-
In this scenario you can set \`to="."\` which will give you access to loosely typed search params.
|
|
8690
|
+
In this scenario you can set \`to="."\` which will give you access to loosely typed search params.
|
|
8670
8691
|
Here is an example that illustrates this:
|
|
8671
8692
|
|
|
8672
8693
|
\`\`\`tsx
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@depup/tanstack__react-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.168.2-depup.0",
|
|
4
4
|
"description": "Modern and scalable routing for React applications (with updated dependencies)",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,10 +87,8 @@
|
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"@tanstack/react-store": "^0.9.2",
|
|
89
89
|
"isbot": "^5.1.36",
|
|
90
|
-
"tiny-invariant": "^1.3.3",
|
|
91
|
-
"tiny-warning": "^1.0.3",
|
|
92
90
|
"@tanstack/history": "1.161.6",
|
|
93
|
-
"@tanstack/router-core": "1.
|
|
91
|
+
"@tanstack/router-core": "1.168.2"
|
|
94
92
|
},
|
|
95
93
|
"devDependencies": {
|
|
96
94
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -128,19 +126,15 @@
|
|
|
128
126
|
},
|
|
129
127
|
"depup": {
|
|
130
128
|
"changes": {
|
|
131
|
-
"@tanstack/react-store": {
|
|
132
|
-
"from": "^0.9.1",
|
|
133
|
-
"to": "^0.9.2"
|
|
134
|
-
},
|
|
135
129
|
"isbot": {
|
|
136
130
|
"from": "^5.1.22",
|
|
137
131
|
"to": "^5.1.36"
|
|
138
132
|
}
|
|
139
133
|
},
|
|
140
|
-
"depsUpdated":
|
|
134
|
+
"depsUpdated": 1,
|
|
141
135
|
"originalPackage": "@tanstack/react-router",
|
|
142
|
-
"originalVersion": "1.
|
|
143
|
-
"processedAt": "2026-03-
|
|
136
|
+
"originalVersion": "1.168.2",
|
|
137
|
+
"processedAt": "2026-03-22T12:15:25.312Z",
|
|
144
138
|
"smokeTest": "passed"
|
|
145
139
|
}
|
|
146
140
|
}
|
package/src/Match.tsx
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import
|
|
3
|
-
import warning from 'tiny-warning'
|
|
2
|
+
import { useStore } from '@tanstack/react-store'
|
|
4
3
|
import {
|
|
5
4
|
createControlledPromise,
|
|
6
5
|
getLocationChangeInfo,
|
|
6
|
+
invariant,
|
|
7
7
|
isNotFound,
|
|
8
8
|
isRedirect,
|
|
9
9
|
rootRouteId,
|
|
10
10
|
} from '@tanstack/router-core'
|
|
11
11
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
12
12
|
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
13
|
-
import { useRouterState } from './useRouterState'
|
|
14
13
|
import { useRouter } from './useRouter'
|
|
15
14
|
import { CatchNotFound } from './not-found'
|
|
16
15
|
import { matchContext } from './matchContext'
|
|
@@ -30,25 +29,98 @@ export const Match = React.memo(function MatchImpl({
|
|
|
30
29
|
matchId: string
|
|
31
30
|
}) {
|
|
32
31
|
const router = useRouter()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
routeId: match.routeId,
|
|
43
|
-
ssr: match.ssr,
|
|
44
|
-
_displayPending: match._displayPending,
|
|
45
|
-
resetKey: s.loadedAt,
|
|
46
|
-
parentRouteId: s.matches[matchIndex - 1]?.routeId as string,
|
|
32
|
+
|
|
33
|
+
if (isServer ?? router.isServer) {
|
|
34
|
+
const match = router.stores.activeMatchStoresById.get(matchId)?.state
|
|
35
|
+
if (!match) {
|
|
36
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`,
|
|
39
|
+
)
|
|
47
40
|
}
|
|
48
|
-
},
|
|
49
|
-
structuralSharing: true as any,
|
|
50
|
-
})
|
|
51
41
|
|
|
42
|
+
invariant()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const routeId = match.routeId as string
|
|
46
|
+
const parentRouteId = (router.routesById[routeId] as AnyRoute).parentRoute
|
|
47
|
+
?.id
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<MatchView
|
|
51
|
+
router={router}
|
|
52
|
+
matchId={matchId}
|
|
53
|
+
resetKey={router.stores.loadedAt.state}
|
|
54
|
+
matchState={{
|
|
55
|
+
routeId,
|
|
56
|
+
ssr: match.ssr,
|
|
57
|
+
_displayPending: match._displayPending,
|
|
58
|
+
parentRouteId,
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Subscribe directly to the match store from the pool.
|
|
65
|
+
// The matchId prop is stable for this component's lifetime (set by Outlet),
|
|
66
|
+
// and reconcileMatchPool reuses stores for the same matchId.
|
|
67
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
68
|
+
const matchStore = router.stores.activeMatchStoresById.get(matchId)
|
|
69
|
+
if (!matchStore) {
|
|
70
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
invariant()
|
|
77
|
+
}
|
|
78
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
79
|
+
const resetKey = useStore(router.stores.loadedAt, (loadedAt) => loadedAt)
|
|
80
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
81
|
+
const match = useStore(matchStore, (value) => value)
|
|
82
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
83
|
+
const matchState = React.useMemo(() => {
|
|
84
|
+
const routeId = match.routeId as string
|
|
85
|
+
const parentRouteId = (router.routesById[routeId] as AnyRoute).parentRoute
|
|
86
|
+
?.id
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
routeId,
|
|
90
|
+
ssr: match.ssr,
|
|
91
|
+
_displayPending: match._displayPending,
|
|
92
|
+
parentRouteId: parentRouteId as string | undefined,
|
|
93
|
+
} satisfies MatchViewState
|
|
94
|
+
}, [match._displayPending, match.routeId, match.ssr, router.routesById])
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<MatchView
|
|
98
|
+
router={router}
|
|
99
|
+
matchId={matchId}
|
|
100
|
+
resetKey={resetKey}
|
|
101
|
+
matchState={matchState}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
type MatchViewState = {
|
|
107
|
+
routeId: string
|
|
108
|
+
ssr: boolean | 'data-only' | undefined
|
|
109
|
+
_displayPending: boolean | undefined
|
|
110
|
+
parentRouteId: string | undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function MatchView({
|
|
114
|
+
router,
|
|
115
|
+
matchId,
|
|
116
|
+
resetKey,
|
|
117
|
+
matchState,
|
|
118
|
+
}: {
|
|
119
|
+
router: ReturnType<typeof useRouter>
|
|
120
|
+
matchId: string
|
|
121
|
+
resetKey: number
|
|
122
|
+
matchState: MatchViewState
|
|
123
|
+
}) {
|
|
52
124
|
const route: AnyRoute = router.routesById[matchState.routeId]
|
|
53
125
|
|
|
54
126
|
const PendingComponent =
|
|
@@ -94,12 +166,14 @@ export const Match = React.memo(function MatchImpl({
|
|
|
94
166
|
<matchContext.Provider value={matchId}>
|
|
95
167
|
<ResolvedSuspenseBoundary fallback={pendingElement}>
|
|
96
168
|
<ResolvedCatchBoundary
|
|
97
|
-
getResetKey={() =>
|
|
169
|
+
getResetKey={() => resetKey}
|
|
98
170
|
errorComponent={routeErrorComponent || ErrorComponent}
|
|
99
171
|
onCatch={(error, errorInfo) => {
|
|
100
172
|
// Forward not found errors (we don't want to show the error component for these)
|
|
101
173
|
if (isNotFound(error)) throw error
|
|
102
|
-
|
|
174
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
175
|
+
console.warn(`Warning: Error in route match: ${matchId}`)
|
|
176
|
+
}
|
|
103
177
|
routeOnCatch?.(error, errorInfo)
|
|
104
178
|
}}
|
|
105
179
|
>
|
|
@@ -137,7 +211,7 @@ export const Match = React.memo(function MatchImpl({
|
|
|
137
211
|
) : null}
|
|
138
212
|
</ShellComponent>
|
|
139
213
|
)
|
|
140
|
-
}
|
|
214
|
+
}
|
|
141
215
|
|
|
142
216
|
// On Rendered can't happen above the root layout because it actually
|
|
143
217
|
// renders a dummy dom element to track the rendered state of the app.
|
|
@@ -165,7 +239,10 @@ function OnRendered() {
|
|
|
165
239
|
) {
|
|
166
240
|
router.emit({
|
|
167
241
|
type: 'onRendered',
|
|
168
|
-
...getLocationChangeInfo(
|
|
242
|
+
...getLocationChangeInfo(
|
|
243
|
+
router.stores.location.state,
|
|
244
|
+
router.stores.resolvedLocation.state,
|
|
245
|
+
),
|
|
169
246
|
})
|
|
170
247
|
prevLocationRef.current = router.latestLocation
|
|
171
248
|
}
|
|
@@ -181,39 +258,123 @@ export const MatchInner = React.memo(function MatchInnerImpl({
|
|
|
181
258
|
}): any {
|
|
182
259
|
const router = useRouter()
|
|
183
260
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
router.options.defaultRemountDeps
|
|
192
|
-
const remountDeps = remountFn?.({
|
|
193
|
-
routeId,
|
|
194
|
-
loaderDeps: match.loaderDeps,
|
|
195
|
-
params: match._strictParams,
|
|
196
|
-
search: match._strictSearch,
|
|
197
|
-
})
|
|
198
|
-
const key = remountDeps ? JSON.stringify(remountDeps) : undefined
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
key,
|
|
202
|
-
routeId,
|
|
203
|
-
match: {
|
|
204
|
-
id: match.id,
|
|
205
|
-
status: match.status,
|
|
206
|
-
error: match.error,
|
|
207
|
-
_forcePending: match._forcePending,
|
|
208
|
-
_displayPending: match._displayPending,
|
|
209
|
-
},
|
|
261
|
+
if (isServer ?? router.isServer) {
|
|
262
|
+
const match = router.stores.activeMatchStoresById.get(matchId)?.state
|
|
263
|
+
if (!match) {
|
|
264
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`,
|
|
267
|
+
)
|
|
210
268
|
}
|
|
211
|
-
},
|
|
212
|
-
structuralSharing: true as any,
|
|
213
|
-
})
|
|
214
269
|
|
|
215
|
-
|
|
270
|
+
invariant()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const routeId = match.routeId as string
|
|
274
|
+
const route = router.routesById[routeId] as AnyRoute
|
|
275
|
+
const remountFn =
|
|
276
|
+
(router.routesById[routeId] as AnyRoute).options.remountDeps ??
|
|
277
|
+
router.options.defaultRemountDeps
|
|
278
|
+
const remountDeps = remountFn?.({
|
|
279
|
+
routeId,
|
|
280
|
+
loaderDeps: match.loaderDeps,
|
|
281
|
+
params: match._strictParams,
|
|
282
|
+
search: match._strictSearch,
|
|
283
|
+
})
|
|
284
|
+
const key = remountDeps ? JSON.stringify(remountDeps) : undefined
|
|
285
|
+
const Comp = route.options.component ?? router.options.defaultComponent
|
|
286
|
+
const out = Comp ? <Comp key={key} /> : <Outlet />
|
|
287
|
+
|
|
288
|
+
if (match._displayPending) {
|
|
289
|
+
throw router.getMatch(match.id)?._nonReactive.displayPendingPromise
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (match._forcePending) {
|
|
293
|
+
throw router.getMatch(match.id)?._nonReactive.minPendingPromise
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (match.status === 'pending') {
|
|
297
|
+
throw router.getMatch(match.id)?._nonReactive.loadPromise
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (match.status === 'notFound') {
|
|
301
|
+
if (!isNotFound(match.error)) {
|
|
302
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
303
|
+
throw new Error('Invariant failed: Expected a notFound error')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
invariant()
|
|
307
|
+
}
|
|
308
|
+
return renderRouteNotFound(router, route, match.error)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (match.status === 'redirected') {
|
|
312
|
+
if (!isRedirect(match.error)) {
|
|
313
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
314
|
+
throw new Error('Invariant failed: Expected a redirect error')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
invariant()
|
|
318
|
+
}
|
|
319
|
+
throw router.getMatch(match.id)?._nonReactive.loadPromise
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (match.status === 'error') {
|
|
323
|
+
const RouteErrorComponent =
|
|
324
|
+
(route.options.errorComponent ??
|
|
325
|
+
router.options.defaultErrorComponent) ||
|
|
326
|
+
ErrorComponent
|
|
327
|
+
return (
|
|
328
|
+
<RouteErrorComponent
|
|
329
|
+
error={match.error as any}
|
|
330
|
+
reset={undefined as any}
|
|
331
|
+
info={{
|
|
332
|
+
componentStack: '',
|
|
333
|
+
}}
|
|
334
|
+
/>
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return out
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
342
|
+
const matchStore = router.stores.activeMatchStoresById.get(matchId)
|
|
343
|
+
if (!matchStore) {
|
|
344
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
345
|
+
throw new Error(
|
|
346
|
+
`Invariant failed: Could not find match for matchId "${matchId}". Please file an issue!`,
|
|
347
|
+
)
|
|
348
|
+
}
|
|
216
349
|
|
|
350
|
+
invariant()
|
|
351
|
+
}
|
|
352
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
353
|
+
const match = useStore(matchStore, (value) => value)
|
|
354
|
+
const routeId = match.routeId as string
|
|
355
|
+
const route = router.routesById[routeId] as AnyRoute
|
|
356
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
357
|
+
const key = React.useMemo(() => {
|
|
358
|
+
const remountFn =
|
|
359
|
+
(router.routesById[routeId] as AnyRoute).options.remountDeps ??
|
|
360
|
+
router.options.defaultRemountDeps
|
|
361
|
+
const remountDeps = remountFn?.({
|
|
362
|
+
routeId,
|
|
363
|
+
loaderDeps: match.loaderDeps,
|
|
364
|
+
params: match._strictParams,
|
|
365
|
+
search: match._strictSearch,
|
|
366
|
+
})
|
|
367
|
+
return remountDeps ? JSON.stringify(remountDeps) : undefined
|
|
368
|
+
}, [
|
|
369
|
+
routeId,
|
|
370
|
+
match.loaderDeps,
|
|
371
|
+
match._strictParams,
|
|
372
|
+
match._strictSearch,
|
|
373
|
+
router.options.defaultRemountDeps,
|
|
374
|
+
router.routesById,
|
|
375
|
+
])
|
|
376
|
+
|
|
377
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
217
378
|
const out = React.useMemo(() => {
|
|
218
379
|
const Comp = route.options.component ?? router.options.defaultComponent
|
|
219
380
|
if (Comp) {
|
|
@@ -256,14 +417,26 @@ export const MatchInner = React.memo(function MatchInnerImpl({
|
|
|
256
417
|
}
|
|
257
418
|
|
|
258
419
|
if (match.status === 'notFound') {
|
|
259
|
-
|
|
420
|
+
if (!isNotFound(match.error)) {
|
|
421
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
422
|
+
throw new Error('Invariant failed: Expected a notFound error')
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
invariant()
|
|
426
|
+
}
|
|
260
427
|
return renderRouteNotFound(router, route, match.error)
|
|
261
428
|
}
|
|
262
429
|
|
|
263
430
|
if (match.status === 'redirected') {
|
|
264
431
|
// Redirects should be handled by the router transition. If we happen to
|
|
265
432
|
// encounter a redirect here, it's a bug. Let's warn, but render nothing.
|
|
266
|
-
|
|
433
|
+
if (!isRedirect(match.error)) {
|
|
434
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
435
|
+
throw new Error('Invariant failed: Expected a redirect error')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
invariant()
|
|
439
|
+
}
|
|
267
440
|
|
|
268
441
|
// warning(
|
|
269
442
|
// false,
|
|
@@ -309,37 +482,57 @@ export const MatchInner = React.memo(function MatchInnerImpl({
|
|
|
309
482
|
export const Outlet = React.memo(function OutletImpl() {
|
|
310
483
|
const router = useRouter()
|
|
311
484
|
const matchId = React.useContext(matchContext)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
485
|
+
|
|
486
|
+
let routeId: string | undefined
|
|
487
|
+
let parentGlobalNotFound = false
|
|
488
|
+
let childMatchId: string | undefined
|
|
489
|
+
|
|
490
|
+
if (isServer ?? router.isServer) {
|
|
491
|
+
const matches = router.stores.activeMatchesSnapshot.state
|
|
492
|
+
const parentIndex = matchId
|
|
493
|
+
? matches.findIndex((match) => match.id === matchId)
|
|
494
|
+
: -1
|
|
495
|
+
const parentMatch = parentIndex >= 0 ? matches[parentIndex] : undefined
|
|
496
|
+
routeId = parentMatch?.routeId as string | undefined
|
|
497
|
+
parentGlobalNotFound = parentMatch?.globalNotFound ?? false
|
|
498
|
+
childMatchId =
|
|
499
|
+
parentIndex >= 0 ? (matches[parentIndex + 1]?.id as string) : undefined
|
|
500
|
+
} else {
|
|
501
|
+
// Subscribe directly to the match store from the pool instead of
|
|
502
|
+
// the two-level byId → matchStore pattern.
|
|
503
|
+
const parentMatchStore = matchId
|
|
504
|
+
? router.stores.activeMatchStoresById.get(matchId)
|
|
505
|
+
: undefined
|
|
506
|
+
|
|
507
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
508
|
+
;[routeId, parentGlobalNotFound] = useStore(parentMatchStore, (match) => [
|
|
509
|
+
match?.routeId as string | undefined,
|
|
510
|
+
match?.globalNotFound ?? false,
|
|
511
|
+
])
|
|
512
|
+
|
|
513
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
514
|
+
childMatchId = useStore(router.stores.matchesId, (ids) => {
|
|
515
|
+
const index = ids.findIndex((id) => id === matchId)
|
|
516
|
+
return ids[index + 1]
|
|
517
|
+
})
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const route = routeId ? router.routesById[routeId] : undefined
|
|
337
521
|
|
|
338
522
|
const pendingElement = router.options.defaultPendingComponent ? (
|
|
339
523
|
<router.options.defaultPendingComponent />
|
|
340
524
|
) : null
|
|
341
525
|
|
|
342
526
|
if (parentGlobalNotFound) {
|
|
527
|
+
if (!route) {
|
|
528
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
529
|
+
throw new Error(
|
|
530
|
+
'Invariant failed: Could not resolve route for Outlet render',
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
invariant()
|
|
535
|
+
}
|
|
343
536
|
return renderRouteNotFound(router, route, undefined)
|
|
344
537
|
}
|
|
345
538
|
|
package/src/Matches.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import
|
|
3
|
-
import { rootRouteId } from '@tanstack/router-core'
|
|
2
|
+
import { useStore } from '@tanstack/react-store'
|
|
3
|
+
import { replaceEqualDeep, rootRouteId } from '@tanstack/router-core'
|
|
4
4
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
5
5
|
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
6
|
-
import { useRouterState } from './useRouterState'
|
|
7
6
|
import { useRouter } from './useRouter'
|
|
8
7
|
import { Transitioner } from './Transitioner'
|
|
9
8
|
import { matchContext } from './matchContext'
|
|
@@ -28,7 +27,6 @@ import type {
|
|
|
28
27
|
ResolveRelativePath,
|
|
29
28
|
ResolveRoute,
|
|
30
29
|
RouteByPath,
|
|
31
|
-
RouterState,
|
|
32
30
|
ToSubOptionsProps,
|
|
33
31
|
} from '@tanstack/router-core'
|
|
34
32
|
|
|
@@ -78,15 +76,15 @@ export function Matches() {
|
|
|
78
76
|
|
|
79
77
|
function MatchesInner() {
|
|
80
78
|
const router = useRouter()
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
const _isServer = isServer ?? router.isServer
|
|
80
|
+
const matchId = _isServer
|
|
81
|
+
? router.stores.firstMatchId.state
|
|
82
|
+
: // eslint-disable-next-line react-hooks/rules-of-hooks
|
|
83
|
+
useStore(router.stores.firstMatchId, (id) => id)
|
|
84
|
+
const resetKey = _isServer
|
|
85
|
+
? router.stores.loadedAt.state
|
|
86
|
+
: // eslint-disable-next-line react-hooks/rules-of-hooks
|
|
87
|
+
useStore(router.stores.loadedAt, (loadedAt) => loadedAt)
|
|
90
88
|
|
|
91
89
|
const matchComponent = matchId ? <Match matchId={matchId} /> : null
|
|
92
90
|
|
|
@@ -101,11 +99,10 @@ function MatchesInner() {
|
|
|
101
99
|
onCatch={
|
|
102
100
|
process.env.NODE_ENV !== 'production'
|
|
103
101
|
? (error) => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`The following error wasn't caught by any route! At the very least, consider setting an 'errorComponent' in your RootRoute!`,
|
|
102
|
+
console.warn(
|
|
103
|
+
`Warning: The following error wasn't caught by any route! At the very least, consider setting an 'errorComponent' in your RootRoute!`,
|
|
107
104
|
)
|
|
108
|
-
|
|
105
|
+
console.warn(`Warning: ${error.message || error.toString()}`)
|
|
109
106
|
}
|
|
110
107
|
: undefined
|
|
111
108
|
}
|
|
@@ -144,10 +141,10 @@ export type UseMatchRouteOptions<
|
|
|
144
141
|
export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
|
|
145
142
|
const router = useRouter()
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
144
|
+
if (!(isServer ?? router.isServer)) {
|
|
145
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
146
|
+
useStore(router.stores.matchRouteReactivity, (d) => d)
|
|
147
|
+
}
|
|
151
148
|
|
|
152
149
|
return React.useCallback(
|
|
153
150
|
<
|
|
@@ -238,15 +235,36 @@ export function useMatches<
|
|
|
238
235
|
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
239
236
|
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
240
237
|
): UseMatchesResult<TRouter, TSelected> {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
const router = useRouter<TRouter>()
|
|
239
|
+
const previousResult =
|
|
240
|
+
React.useRef<ValidateSelected<TRouter, TSelected, TStructuralSharing>>(
|
|
241
|
+
undefined,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if (isServer ?? router.isServer) {
|
|
245
|
+
const matches = router.stores.activeMatchesSnapshot.state as Array<
|
|
246
|
+
MakeRouteMatchUnion<TRouter>
|
|
247
|
+
>
|
|
248
|
+
return (opts?.select ? opts.select(matches) : matches) as UseMatchesResult<
|
|
249
|
+
TRouter,
|
|
250
|
+
TSelected
|
|
251
|
+
>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
255
|
+
return useStore(router.stores.activeMatchesSnapshot, (matches) => {
|
|
256
|
+
const selected = opts?.select
|
|
257
|
+
? opts.select(matches as Array<MakeRouteMatchUnion<TRouter>>)
|
|
258
|
+
: (matches as any)
|
|
259
|
+
|
|
260
|
+
if (opts?.structuralSharing ?? router.options.defaultStructuralSharing) {
|
|
261
|
+
const shared = replaceEqualDeep(previousResult.current, selected)
|
|
262
|
+
previousResult.current = shared
|
|
263
|
+
return shared
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return selected
|
|
267
|
+
}) as UseMatchesResult<TRouter, TSelected>
|
|
250
268
|
}
|
|
251
269
|
|
|
252
270
|
/**
|