@tanstack/react-router 0.0.1-beta.9 → 1.0.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.
- package/LICENSE +21 -0
- package/build/cjs/CatchBoundary.js +128 -0
- package/build/cjs/CatchBoundary.js.map +1 -0
- package/build/cjs/Matches.js +233 -0
- package/build/cjs/Matches.js.map +1 -0
- package/build/cjs/RouterProvider.js +172 -0
- package/build/cjs/RouterProvider.js.map +1 -0
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +2 -22
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
- package/build/cjs/awaited.js +43 -0
- package/build/cjs/awaited.js.map +1 -0
- package/build/cjs/defer.js +37 -0
- package/build/cjs/defer.js.map +1 -0
- package/build/cjs/fileRoute.js +27 -0
- package/build/cjs/fileRoute.js.map +1 -0
- package/build/cjs/index.js +130 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/lazyRouteComponent.js +54 -0
- package/build/cjs/lazyRouteComponent.js.map +1 -0
- package/build/cjs/link.js +223 -0
- package/build/cjs/link.js.map +1 -0
- package/build/cjs/path.js +214 -0
- package/build/cjs/path.js.map +1 -0
- package/build/cjs/qss.js +63 -0
- package/build/cjs/qss.js.map +1 -0
- package/build/cjs/redirects.js +28 -0
- package/build/cjs/redirects.js.map +1 -0
- package/build/cjs/route.js +191 -0
- package/build/cjs/route.js.map +1 -0
- package/build/cjs/router.js +1085 -0
- package/build/cjs/router.js.map +1 -0
- package/build/cjs/scroll-restoration.js +202 -0
- package/build/cjs/scroll-restoration.js.map +1 -0
- package/build/cjs/searchParams.js +81 -0
- package/build/cjs/searchParams.js.map +1 -0
- package/build/cjs/useBlocker.js +55 -0
- package/build/cjs/useBlocker.js.map +1 -0
- package/build/cjs/useNavigate.js +86 -0
- package/build/cjs/useNavigate.js.map +1 -0
- package/build/cjs/useParams.js +26 -0
- package/build/cjs/useParams.js.map +1 -0
- package/build/cjs/useSearch.js +25 -0
- package/build/cjs/useSearch.js.map +1 -0
- package/build/cjs/utils.js +241 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +2302 -2534
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +3498 -2694
- package/build/stats-react.json +1204 -44
- package/build/types/CatchBoundary.d.ts +36 -0
- package/build/types/Matches.d.ts +64 -0
- package/build/types/RouterProvider.d.ts +35 -0
- package/build/types/awaited.d.ts +9 -0
- package/build/types/defer.d.ts +19 -0
- package/build/types/fileRoute.d.ts +38 -0
- package/build/types/history.d.ts +7 -0
- package/build/types/index.d.ts +27 -74
- package/build/types/lazyRouteComponent.d.ts +2 -0
- package/build/types/link.d.ts +93 -0
- package/build/types/location.d.ts +12 -0
- package/build/types/path.d.ts +17 -0
- package/build/types/qss.d.ts +2 -0
- package/build/types/redirects.d.ts +11 -0
- package/build/types/route.d.ts +283 -0
- package/build/types/routeInfo.d.ts +31 -0
- package/build/types/router.d.ts +186 -0
- package/build/types/scroll-restoration.d.ts +18 -0
- package/build/types/searchParams.d.ts +7 -0
- package/build/types/useBlocker.d.ts +9 -0
- package/build/types/useNavigate.d.ts +19 -0
- package/build/types/useParams.d.ts +7 -0
- package/build/types/useSearch.d.ts +7 -0
- package/build/types/utils.d.ts +69 -0
- package/build/umd/index.development.js +2899 -2493
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +4 -4
- package/build/umd/index.production.js.map +1 -1
- package/package.json +12 -10
- package/src/CatchBoundary.tsx +101 -0
- package/src/Matches.tsx +423 -0
- package/src/RouterProvider.tsx +254 -0
- package/src/awaited.tsx +40 -0
- package/src/defer.ts +55 -0
- package/src/fileRoute.ts +152 -0
- package/src/history.ts +8 -0
- package/src/index.tsx +28 -619
- package/src/lazyRouteComponent.tsx +33 -0
- package/src/link.tsx +603 -0
- package/src/location.ts +13 -0
- package/src/path.ts +261 -0
- package/src/qss.ts +53 -0
- package/src/redirects.ts +39 -0
- package/src/route.ts +882 -0
- package/src/routeInfo.ts +84 -0
- package/src/router.ts +1671 -0
- package/src/scroll-restoration.tsx +230 -0
- package/src/searchParams.ts +79 -0
- package/src/useBlocker.tsx +27 -0
- package/src/useNavigate.tsx +111 -0
- package/src/useParams.tsx +25 -0
- package/src/useSearch.tsx +25 -0
- package/src/utils.ts +360 -0
- package/build/cjs/react-router/src/index.js +0 -458
- package/build/cjs/react-router/src/index.js.map +0 -1
- package/build/cjs/router-core/build/esm/index.js +0 -2524
- package/build/cjs/router-core/build/esm/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-router",
|
|
3
3
|
"author": "Tanner Linsley",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "tanstack/router",
|
|
7
|
-
"homepage": "https://tanstack.com/router
|
|
7
|
+
"homepage": "https://tanstack.com/router",
|
|
8
8
|
"description": "",
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"keywords": [
|
|
13
13
|
"react",
|
|
14
14
|
"location",
|
|
15
|
-
"@tanstack/react-router",
|
|
16
15
|
"router",
|
|
17
16
|
"routing",
|
|
18
17
|
"async",
|
|
@@ -24,7 +23,7 @@
|
|
|
24
23
|
"url": "https://github.com/sponsors/tannerlinsley"
|
|
25
24
|
},
|
|
26
25
|
"module": "build/esm/index.js",
|
|
27
|
-
"main": "build/cjs/
|
|
26
|
+
"main": "build/cjs/index.js",
|
|
28
27
|
"browser": "build/umd/index.production.js",
|
|
29
28
|
"types": "build/types/index.d.ts",
|
|
30
29
|
"engines": {
|
|
@@ -34,17 +33,20 @@
|
|
|
34
33
|
"build/**",
|
|
35
34
|
"src"
|
|
36
35
|
],
|
|
36
|
+
"sideEffects": false,
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": ">=16",
|
|
39
39
|
"react-dom": ">=16"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@babel/runtime": "^7.16.7",
|
|
43
|
-
"@tanstack/
|
|
44
|
-
"
|
|
43
|
+
"@tanstack/react-store": "^0.2.1",
|
|
44
|
+
"@tanstack/store": "^0.1.3",
|
|
45
|
+
"tiny-invariant": "^1.3.1",
|
|
46
|
+
"tiny-warning": "^1.0.3",
|
|
47
|
+
"@tanstack/history": "1.0.1"
|
|
45
48
|
},
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"babel-plugin-transform-async-to-promises": "^0.8.18"
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "rollup --config rollup.config.js"
|
|
49
51
|
}
|
|
50
|
-
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
export function CatchBoundary(props: {
|
|
4
|
+
getResetKey: () => string
|
|
5
|
+
children: any
|
|
6
|
+
errorComponent?: any
|
|
7
|
+
onCatch: (error: any) => void
|
|
8
|
+
}) {
|
|
9
|
+
const errorComponent = props.errorComponent ?? ErrorComponent
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<CatchBoundaryImpl
|
|
13
|
+
getResetKey={props.getResetKey}
|
|
14
|
+
onCatch={props.onCatch}
|
|
15
|
+
children={({ error }) => {
|
|
16
|
+
if (error) {
|
|
17
|
+
return React.createElement(errorComponent, {
|
|
18
|
+
error,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return props.children
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class CatchBoundaryImpl extends React.Component<{
|
|
29
|
+
getResetKey: () => string
|
|
30
|
+
children: (props: { error: any; reset: () => void }) => any
|
|
31
|
+
onCatch?: (error: any) => void
|
|
32
|
+
}> {
|
|
33
|
+
state = { error: null } as any
|
|
34
|
+
static getDerivedStateFromProps(props: any) {
|
|
35
|
+
return { resetKey: props.getResetKey() }
|
|
36
|
+
}
|
|
37
|
+
static getDerivedStateFromError(error: any) {
|
|
38
|
+
return { error }
|
|
39
|
+
}
|
|
40
|
+
componentDidUpdate(
|
|
41
|
+
prevProps: Readonly<{
|
|
42
|
+
getResetKey: () => string
|
|
43
|
+
children: (props: { error: any; reset: () => void }) => any
|
|
44
|
+
onCatch?: ((error: any, info: any) => void) | undefined
|
|
45
|
+
}>,
|
|
46
|
+
prevState: any,
|
|
47
|
+
): void {
|
|
48
|
+
if (prevState.error && prevState.resetKey !== this.state.resetKey) {
|
|
49
|
+
this.setState({ error: null })
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
componentDidCatch(error: any) {
|
|
53
|
+
console.error(error)
|
|
54
|
+
this.props.onCatch?.(error)
|
|
55
|
+
}
|
|
56
|
+
render() {
|
|
57
|
+
return this.props.children(this.state)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ErrorComponent({ error }: { error: any }) {
|
|
62
|
+
const [show, setShow] = React.useState(process.env.NODE_ENV !== 'production')
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div style={{ padding: '.5rem', maxWidth: '100%' }}>
|
|
66
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
|
|
67
|
+
<strong style={{ fontSize: '1rem' }}>Something went wrong!</strong>
|
|
68
|
+
<button
|
|
69
|
+
style={{
|
|
70
|
+
appearance: 'none',
|
|
71
|
+
fontSize: '.6em',
|
|
72
|
+
border: '1px solid currentColor',
|
|
73
|
+
padding: '.1rem .2rem',
|
|
74
|
+
fontWeight: 'bold',
|
|
75
|
+
borderRadius: '.25rem',
|
|
76
|
+
}}
|
|
77
|
+
onClick={() => setShow((d) => !d)}
|
|
78
|
+
>
|
|
79
|
+
{show ? 'Hide Error' : 'Show Error'}
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
<div style={{ height: '.25rem' }} />
|
|
83
|
+
{show ? (
|
|
84
|
+
<div>
|
|
85
|
+
<pre
|
|
86
|
+
style={{
|
|
87
|
+
fontSize: '.7em',
|
|
88
|
+
border: '1px solid red',
|
|
89
|
+
borderRadius: '.25rem',
|
|
90
|
+
padding: '.3rem',
|
|
91
|
+
color: 'red',
|
|
92
|
+
overflow: 'auto',
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{error.message ? <code>{error.message}</code> : null}
|
|
96
|
+
</pre>
|
|
97
|
+
</div>
|
|
98
|
+
) : null}
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|
package/src/Matches.tsx
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import invariant from 'tiny-invariant'
|
|
3
|
+
import warning from 'tiny-warning'
|
|
4
|
+
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
5
|
+
import { useRouter, useRouterState } from './RouterProvider'
|
|
6
|
+
import { ResolveRelativePath, ToOptions } from './link'
|
|
7
|
+
import { AnyRoute, ReactNode, rootRouteId } from './route'
|
|
8
|
+
import {
|
|
9
|
+
FullSearchSchema,
|
|
10
|
+
ParseRoute,
|
|
11
|
+
RouteById,
|
|
12
|
+
RouteByPath,
|
|
13
|
+
RouteIds,
|
|
14
|
+
RoutePaths,
|
|
15
|
+
} from './routeInfo'
|
|
16
|
+
import { RegisteredRouter, RouterState } from './router'
|
|
17
|
+
import { NoInfer, StrictOrFrom, pick } from './utils'
|
|
18
|
+
|
|
19
|
+
export const matchContext = React.createContext<string | undefined>(undefined)
|
|
20
|
+
|
|
21
|
+
export interface RouteMatch<
|
|
22
|
+
TRouteTree extends AnyRoute = AnyRoute,
|
|
23
|
+
TRouteId extends RouteIds<TRouteTree> = ParseRoute<TRouteTree>['id'],
|
|
24
|
+
> {
|
|
25
|
+
id: string
|
|
26
|
+
routeId: TRouteId
|
|
27
|
+
pathname: string
|
|
28
|
+
params: RouteById<TRouteTree, TRouteId>['types']['allParams']
|
|
29
|
+
status: 'pending' | 'success' | 'error'
|
|
30
|
+
isFetching: boolean
|
|
31
|
+
showPending: boolean
|
|
32
|
+
error: unknown
|
|
33
|
+
paramsError: unknown
|
|
34
|
+
searchError: unknown
|
|
35
|
+
updatedAt: number
|
|
36
|
+
loadPromise?: Promise<void>
|
|
37
|
+
loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData']
|
|
38
|
+
routeContext: RouteById<TRouteTree, TRouteId>['types']['routeContext']
|
|
39
|
+
context: RouteById<TRouteTree, TRouteId>['types']['allContext']
|
|
40
|
+
search: FullSearchSchema<TRouteTree> &
|
|
41
|
+
RouteById<TRouteTree, TRouteId>['types']['fullSearchSchema']
|
|
42
|
+
fetchCount: number
|
|
43
|
+
abortController: AbortController
|
|
44
|
+
cause: 'preload' | 'enter' | 'stay'
|
|
45
|
+
loaderDeps: RouteById<TRouteTree, TRouteId>['types']['loaderDeps']
|
|
46
|
+
preload: boolean
|
|
47
|
+
invalid: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type AnyRouteMatch = RouteMatch<any, any>
|
|
51
|
+
|
|
52
|
+
export function Matches() {
|
|
53
|
+
const router = useRouter()
|
|
54
|
+
const matchId = useRouterState({
|
|
55
|
+
select: (s) => {
|
|
56
|
+
return getRenderedMatches(s)[0]?.id
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<matchContext.Provider value={matchId}>
|
|
62
|
+
<CatchBoundary
|
|
63
|
+
getResetKey={() => router.state.resolvedLocation.state?.key}
|
|
64
|
+
errorComponent={ErrorComponent}
|
|
65
|
+
onCatch={() => {
|
|
66
|
+
warning(
|
|
67
|
+
false,
|
|
68
|
+
`Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`,
|
|
69
|
+
)
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{matchId ? <Match matchId={matchId} /> : null}
|
|
73
|
+
</CatchBoundary>
|
|
74
|
+
</matchContext.Provider>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function SafeFragment(props: any) {
|
|
79
|
+
return <>{props.children}</>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function Match({ matchId }: { matchId: string }) {
|
|
83
|
+
const router = useRouter()
|
|
84
|
+
const routeId = useRouterState({
|
|
85
|
+
select: (s) =>
|
|
86
|
+
getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
invariant(
|
|
90
|
+
routeId,
|
|
91
|
+
`Could not find routeId for matchId "${matchId}". Please file an issue!`,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const route = router.routesById[routeId]!
|
|
95
|
+
|
|
96
|
+
const PendingComponent = (route.options.pendingComponent ??
|
|
97
|
+
router.options.defaultPendingComponent) as any
|
|
98
|
+
|
|
99
|
+
const pendingElement = PendingComponent ? <PendingComponent /> : null
|
|
100
|
+
|
|
101
|
+
const routeErrorComponent =
|
|
102
|
+
route.options.errorComponent ??
|
|
103
|
+
router.options.defaultErrorComponent ??
|
|
104
|
+
ErrorComponent
|
|
105
|
+
|
|
106
|
+
const ResolvedSuspenseBoundary =
|
|
107
|
+
route.options.wrapInSuspense ??
|
|
108
|
+
PendingComponent ??
|
|
109
|
+
route.options.component?.preload ??
|
|
110
|
+
route.options.pendingComponent?.preload ??
|
|
111
|
+
(route.options.errorComponent as any)?.preload
|
|
112
|
+
? React.Suspense
|
|
113
|
+
: SafeFragment
|
|
114
|
+
|
|
115
|
+
const ResolvedCatchBoundary = routeErrorComponent
|
|
116
|
+
? CatchBoundary
|
|
117
|
+
: SafeFragment
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<matchContext.Provider value={matchId}>
|
|
121
|
+
<ResolvedSuspenseBoundary fallback={pendingElement}>
|
|
122
|
+
<ResolvedCatchBoundary
|
|
123
|
+
getResetKey={() => router.state.resolvedLocation.state?.key}
|
|
124
|
+
errorComponent={routeErrorComponent}
|
|
125
|
+
onCatch={() => {
|
|
126
|
+
warning(false, `Error in route match: ${matchId}`)
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
<MatchInner matchId={matchId!} pendingElement={pendingElement} />
|
|
130
|
+
</ResolvedCatchBoundary>
|
|
131
|
+
</ResolvedSuspenseBoundary>
|
|
132
|
+
</matchContext.Provider>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function MatchInner({
|
|
137
|
+
matchId,
|
|
138
|
+
pendingElement,
|
|
139
|
+
}: {
|
|
140
|
+
matchId: string
|
|
141
|
+
pendingElement: any
|
|
142
|
+
}): any {
|
|
143
|
+
const router = useRouter()
|
|
144
|
+
const routeId = useRouterState({
|
|
145
|
+
select: (s) =>
|
|
146
|
+
getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const route = router.routesById[routeId]!
|
|
150
|
+
|
|
151
|
+
const match = useRouterState({
|
|
152
|
+
select: (s) =>
|
|
153
|
+
pick(getRenderedMatches(s).find((d) => d.id === matchId)!, [
|
|
154
|
+
'status',
|
|
155
|
+
'error',
|
|
156
|
+
'showPending',
|
|
157
|
+
'loadPromise',
|
|
158
|
+
]),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (match.status === 'error') {
|
|
162
|
+
throw match.error
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (match.status === 'pending') {
|
|
166
|
+
if (match.showPending) {
|
|
167
|
+
return pendingElement
|
|
168
|
+
}
|
|
169
|
+
throw match.loadPromise
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (match.status === 'success') {
|
|
173
|
+
let Comp = route.options.component ?? router.options.defaultComponent
|
|
174
|
+
|
|
175
|
+
if (Comp) {
|
|
176
|
+
return <Comp />
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return <Outlet />
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
invariant(
|
|
183
|
+
false,
|
|
184
|
+
'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const Outlet = React.memo(function Outlet() {
|
|
189
|
+
const matchId = React.useContext(matchContext)
|
|
190
|
+
|
|
191
|
+
const childMatchId = useRouterState({
|
|
192
|
+
select: (s) => {
|
|
193
|
+
const matches = getRenderedMatches(s)
|
|
194
|
+
const index = matches.findIndex((d) => d.id === matchId)
|
|
195
|
+
return matches[index + 1]?.id
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
if (!childMatchId) {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return <Match matchId={childMatchId} />
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
export interface MatchRouteOptions {
|
|
207
|
+
pending?: boolean
|
|
208
|
+
caseSensitive?: boolean
|
|
209
|
+
includeSearch?: boolean
|
|
210
|
+
fuzzy?: boolean
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export type UseMatchRouteOptions<
|
|
214
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
215
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
216
|
+
TTo extends string = '',
|
|
217
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
218
|
+
TMaskTo extends string = '',
|
|
219
|
+
> = ToOptions<AnyRoute, TFrom, TTo, TMaskFrom, TMaskTo> & MatchRouteOptions
|
|
220
|
+
|
|
221
|
+
export function useMatchRoute<
|
|
222
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
223
|
+
>() {
|
|
224
|
+
useRouterState({ select: (s) => [s.location, s.resolvedLocation] })
|
|
225
|
+
const { matchRoute } = useRouter()
|
|
226
|
+
|
|
227
|
+
return React.useCallback(
|
|
228
|
+
<
|
|
229
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
230
|
+
TTo extends string = '',
|
|
231
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
232
|
+
TMaskTo extends string = '',
|
|
233
|
+
TResolved extends string = ResolveRelativePath<TFrom, NoInfer<TTo>>,
|
|
234
|
+
>(
|
|
235
|
+
opts: UseMatchRouteOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
236
|
+
): false | RouteById<TRouteTree, TResolved>['types']['allParams'] => {
|
|
237
|
+
const { pending, caseSensitive, ...rest } = opts
|
|
238
|
+
|
|
239
|
+
return matchRoute(rest as any, {
|
|
240
|
+
pending,
|
|
241
|
+
caseSensitive,
|
|
242
|
+
})
|
|
243
|
+
},
|
|
244
|
+
[],
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export type MakeMatchRouteOptions<
|
|
249
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
250
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
251
|
+
TTo extends string = '',
|
|
252
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
253
|
+
TMaskTo extends string = '',
|
|
254
|
+
> = ToOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
255
|
+
MatchRouteOptions & {
|
|
256
|
+
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
|
|
257
|
+
children?:
|
|
258
|
+
| ((
|
|
259
|
+
params?: RouteByPath<
|
|
260
|
+
TRouteTree,
|
|
261
|
+
ResolveRelativePath<TFrom, NoInfer<TTo>>
|
|
262
|
+
>['types']['allParams'],
|
|
263
|
+
) => ReactNode)
|
|
264
|
+
| React.ReactNode
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function MatchRoute<
|
|
268
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
269
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
270
|
+
TTo extends string = '',
|
|
271
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
272
|
+
TMaskTo extends string = '',
|
|
273
|
+
>(
|
|
274
|
+
props: MakeMatchRouteOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
275
|
+
): any {
|
|
276
|
+
const matchRoute = useMatchRoute()
|
|
277
|
+
const params = matchRoute(props as any)
|
|
278
|
+
|
|
279
|
+
if (typeof props.children === 'function') {
|
|
280
|
+
return (props.children as any)(params)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return !!params ? props.children : null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getRenderedMatches(state: RouterState) {
|
|
287
|
+
return state.pendingMatches?.some((d) => d.showPending)
|
|
288
|
+
? state.pendingMatches
|
|
289
|
+
: state.matches
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function useMatch<
|
|
293
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
294
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
295
|
+
TStrict extends boolean = true,
|
|
296
|
+
TRouteMatchState = RouteMatch<TRouteTree, TFrom>,
|
|
297
|
+
TSelected = TRouteMatchState,
|
|
298
|
+
>(
|
|
299
|
+
opts: StrictOrFrom<TFrom> & {
|
|
300
|
+
select?: (match: TRouteMatchState) => TSelected
|
|
301
|
+
},
|
|
302
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
303
|
+
const router = useRouter()
|
|
304
|
+
const nearestMatchId = React.useContext(matchContext)
|
|
305
|
+
|
|
306
|
+
const nearestMatchRouteId = getRenderedMatches(router.state).find(
|
|
307
|
+
(d) => d.id === nearestMatchId,
|
|
308
|
+
)?.routeId
|
|
309
|
+
|
|
310
|
+
const matchRouteId = (() => {
|
|
311
|
+
const matches = getRenderedMatches(router.state)
|
|
312
|
+
const match = opts?.from
|
|
313
|
+
? matches.find((d) => d.routeId === opts?.from)
|
|
314
|
+
: matches.find((d) => d.id === nearestMatchId)
|
|
315
|
+
return match!.routeId
|
|
316
|
+
})()
|
|
317
|
+
|
|
318
|
+
if (opts?.strict ?? true) {
|
|
319
|
+
invariant(
|
|
320
|
+
nearestMatchRouteId == matchRouteId,
|
|
321
|
+
`useMatch("${
|
|
322
|
+
matchRouteId as string
|
|
323
|
+
}") is being called in a component that is meant to render the '${nearestMatchRouteId}' route. Did you mean to 'useMatch("${
|
|
324
|
+
matchRouteId as string
|
|
325
|
+
}", { strict: false })' or 'useRoute("${
|
|
326
|
+
matchRouteId as string
|
|
327
|
+
}")' instead?`,
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const matchSelection = useRouterState({
|
|
332
|
+
select: (state) => {
|
|
333
|
+
const match = getRenderedMatches(state).find(
|
|
334
|
+
(d) => d.id === nearestMatchId,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
invariant(
|
|
338
|
+
match,
|
|
339
|
+
`Could not find ${
|
|
340
|
+
opts?.from
|
|
341
|
+
? `an active match from "${opts.from}"`
|
|
342
|
+
: 'a nearest match!'
|
|
343
|
+
}`,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return opts?.select ? opts.select(match as any) : match
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
return matchSelection as any
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function useMatches<T = RouteMatch[]>(opts?: {
|
|
354
|
+
select?: (matches: RouteMatch[]) => T
|
|
355
|
+
}): T {
|
|
356
|
+
return useRouterState({
|
|
357
|
+
select: (state) => {
|
|
358
|
+
let matches = getRenderedMatches(state)
|
|
359
|
+
return opts?.select ? opts.select(matches) : (matches as T)
|
|
360
|
+
},
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function useParentMatches<T = RouteMatch[]>(opts?: {
|
|
365
|
+
select?: (matches: RouteMatch[]) => T
|
|
366
|
+
}): T {
|
|
367
|
+
const contextMatchId = React.useContext(matchContext)
|
|
368
|
+
|
|
369
|
+
return useMatches({
|
|
370
|
+
select: (matches) => {
|
|
371
|
+
matches = matches.slice(matches.findIndex((d) => d.id === contextMatchId))
|
|
372
|
+
return opts?.select ? opts.select(matches) : (matches as T)
|
|
373
|
+
},
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function useLoaderDeps<
|
|
378
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
379
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
380
|
+
TStrict extends boolean = true,
|
|
381
|
+
TRouteMatch extends RouteMatch<TRouteTree, TFrom> = RouteMatch<
|
|
382
|
+
TRouteTree,
|
|
383
|
+
TFrom
|
|
384
|
+
>,
|
|
385
|
+
TSelected = Required<TRouteMatch>['loaderDeps'],
|
|
386
|
+
>(
|
|
387
|
+
opts: StrictOrFrom<TFrom> & {
|
|
388
|
+
select?: (match: TRouteMatch) => TSelected
|
|
389
|
+
},
|
|
390
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
391
|
+
return useMatch({
|
|
392
|
+
...opts,
|
|
393
|
+
select: (s) => {
|
|
394
|
+
return typeof opts.select === 'function'
|
|
395
|
+
? opts.select(s?.loaderDeps)
|
|
396
|
+
: s?.loaderDeps
|
|
397
|
+
},
|
|
398
|
+
})!
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function useLoaderData<
|
|
402
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
403
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
404
|
+
TStrict extends boolean = true,
|
|
405
|
+
TRouteMatch extends RouteMatch<TRouteTree, TFrom> = RouteMatch<
|
|
406
|
+
TRouteTree,
|
|
407
|
+
TFrom
|
|
408
|
+
>,
|
|
409
|
+
TSelected = Required<TRouteMatch>['loaderData'],
|
|
410
|
+
>(
|
|
411
|
+
opts: StrictOrFrom<TFrom> & {
|
|
412
|
+
select?: (match: TRouteMatch) => TSelected
|
|
413
|
+
},
|
|
414
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
415
|
+
return useMatch({
|
|
416
|
+
...opts,
|
|
417
|
+
select: (s) => {
|
|
418
|
+
return typeof opts.select === 'function'
|
|
419
|
+
? opts.select(s?.loaderData)
|
|
420
|
+
: s?.loaderData
|
|
421
|
+
},
|
|
422
|
+
})!
|
|
423
|
+
}
|