@tanstack/react-router 0.0.1-beta.8 → 0.0.1-beta.81
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/_virtual/_rollupPluginBabelHelpers.js +0 -18
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
- package/build/cjs/index.js +468 -0
- package/build/cjs/index.js.map +1 -0
- package/build/esm/index.js +341 -2818
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +148 -35
- package/build/types/index.d.ts +87 -49
- package/build/umd/index.development.js +1802 -2454
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +23 -3
- package/build/umd/index.production.js.map +1 -1
- package/package.json +6 -6
- package/src/index.tsx +606 -478
- package/build/cjs/react-router/src/index.js +0 -459
- 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/src/index.tsx
CHANGED
|
@@ -1,141 +1,71 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
|
|
3
|
-
import { useSyncExternalStore } from 'use-sync-external-store/shim'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
AnyRoute,
|
|
7
|
-
CheckId,
|
|
8
|
-
rootRouteId,
|
|
9
|
-
Router,
|
|
10
|
-
RouterState,
|
|
11
|
-
ToIdOption,
|
|
12
|
-
} from '@tanstack/router-core'
|
|
13
3
|
import {
|
|
4
|
+
RegisteredRoutesInfo,
|
|
5
|
+
RegisteredRouter,
|
|
6
|
+
RouterStore,
|
|
7
|
+
last,
|
|
14
8
|
warning,
|
|
15
9
|
RouterOptions,
|
|
16
10
|
RouteMatch,
|
|
17
11
|
MatchRouteOptions,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
AnyAllRouteInfo,
|
|
21
|
-
DefaultAllRouteInfo,
|
|
12
|
+
AnyRoutesInfo,
|
|
13
|
+
DefaultRoutesInfo,
|
|
22
14
|
functionalUpdate,
|
|
23
|
-
|
|
24
|
-
AnyRouteInfo,
|
|
25
|
-
AllRouteInfo,
|
|
26
|
-
RouteInfo,
|
|
15
|
+
RoutesInfo,
|
|
27
16
|
ValidFromPath,
|
|
28
17
|
LinkOptions,
|
|
29
|
-
|
|
18
|
+
RouteByPath,
|
|
30
19
|
ResolveRelativePath,
|
|
31
20
|
NoInfer,
|
|
32
21
|
ToOptions,
|
|
33
22
|
invariant,
|
|
34
|
-
|
|
23
|
+
Router,
|
|
24
|
+
AnyRootRoute,
|
|
25
|
+
RootRoute,
|
|
26
|
+
AnyRouteMatch,
|
|
27
|
+
NavigateOptions,
|
|
28
|
+
RouterConstructorOptions,
|
|
29
|
+
} from '@tanstack/router'
|
|
30
|
+
import { useStore } from '@tanstack/react-store'
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
//
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
interface FrameworkGenerics {
|
|
40
|
-
Element: React.ReactNode
|
|
41
|
-
// Any is required here so import() will work without having to do import().then(d => d.default)
|
|
42
|
-
SyncOrAsyncElement: React.ReactNode | (() => Promise<any>)
|
|
43
|
-
}
|
|
34
|
+
export * from '@tanstack/router'
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
47
|
-
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
48
|
-
> {
|
|
49
|
-
useState: () => RouterState
|
|
50
|
-
useRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
51
|
-
routeId: TId,
|
|
52
|
-
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
53
|
-
useMatch: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
54
|
-
routeId: TId,
|
|
55
|
-
) => RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
56
|
-
linkProps: <TTo extends string = '.'>(
|
|
57
|
-
props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
|
|
58
|
-
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
59
|
-
) => React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
60
|
-
Link: <TTo extends string = '.'>(
|
|
61
|
-
props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
|
|
62
|
-
React.AnchorHTMLAttributes<HTMLAnchorElement> &
|
|
63
|
-
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
|
|
64
|
-
// 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
|
|
65
|
-
children?:
|
|
66
|
-
| React.ReactNode
|
|
67
|
-
| ((state: { isActive: boolean }) => React.ReactNode)
|
|
68
|
-
},
|
|
69
|
-
) => JSX.Element
|
|
70
|
-
MatchRoute: <TTo extends string = '.'>(
|
|
71
|
-
props: ToOptions<TAllRouteInfo, '/', TTo> &
|
|
72
|
-
MatchRouteOptions & {
|
|
73
|
-
// 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
|
|
74
|
-
children?:
|
|
75
|
-
| React.ReactNode
|
|
76
|
-
| ((
|
|
77
|
-
params: RouteInfoByPath<
|
|
78
|
-
TAllRouteInfo,
|
|
79
|
-
ResolveRelativePath<'/', NoInfer<TTo>>
|
|
80
|
-
>['allParams'],
|
|
81
|
-
) => React.ReactNode)
|
|
82
|
-
},
|
|
83
|
-
) => JSX.Element
|
|
84
|
-
}
|
|
36
|
+
export { useStore }
|
|
85
37
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
props: LinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
|
|
109
|
-
React.AnchorHTMLAttributes<HTMLAnchorElement> &
|
|
110
|
-
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
|
|
111
|
-
// 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
|
|
112
|
-
children?:
|
|
113
|
-
| React.ReactNode
|
|
114
|
-
| ((state: { isActive: boolean }) => React.ReactNode)
|
|
115
|
-
},
|
|
116
|
-
) => JSX.Element
|
|
117
|
-
MatchRoute: <TTo extends string = '.'>(
|
|
118
|
-
props: ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
|
|
119
|
-
MatchRouteOptions & {
|
|
120
|
-
// 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
|
|
121
|
-
children?:
|
|
122
|
-
| React.ReactNode
|
|
123
|
-
| ((
|
|
124
|
-
params: RouteInfoByPath<
|
|
125
|
-
TAllRouteInfo,
|
|
126
|
-
ResolveRelativePath<TRouteInfo['fullPath'], NoInfer<TTo>>
|
|
127
|
-
>['allParams'],
|
|
128
|
-
) => React.ReactNode)
|
|
129
|
-
},
|
|
130
|
-
) => JSX.Element
|
|
38
|
+
//
|
|
39
|
+
|
|
40
|
+
type ReactNode = any
|
|
41
|
+
|
|
42
|
+
export type SyncRouteComponent<TProps = {}> = (props: TProps) => ReactNode
|
|
43
|
+
|
|
44
|
+
export type RouteComponent<TProps = {}> = SyncRouteComponent<TProps> & {
|
|
45
|
+
preload?: () => Promise<void>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function lazy(
|
|
49
|
+
importer: () => Promise<{ default: SyncRouteComponent }>,
|
|
50
|
+
): RouteComponent {
|
|
51
|
+
const lazyComp = React.lazy(importer as any)
|
|
52
|
+
let preloaded: Promise<SyncRouteComponent>
|
|
53
|
+
|
|
54
|
+
const finalComp = lazyComp as unknown as RouteComponent
|
|
55
|
+
|
|
56
|
+
finalComp.preload = async () => {
|
|
57
|
+
if (!preloaded) {
|
|
58
|
+
await importer()
|
|
59
|
+
}
|
|
131
60
|
}
|
|
61
|
+
|
|
62
|
+
return finalComp
|
|
132
63
|
}
|
|
133
64
|
|
|
134
|
-
type LinkPropsOptions<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
> = LinkOptions<TAllRouteInfo, TFrom, TTo> & {
|
|
65
|
+
export type LinkPropsOptions<
|
|
66
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
67
|
+
TTo extends string = '',
|
|
68
|
+
> = LinkOptions<RegisteredRoutesInfo, TFrom, TTo> & {
|
|
139
69
|
// A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
140
70
|
activeProps?:
|
|
141
71
|
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
@@ -146,475 +76,673 @@ type LinkPropsOptions<
|
|
|
146
76
|
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
147
77
|
}
|
|
148
78
|
|
|
79
|
+
export type MakeUseMatchRouteOptions<
|
|
80
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
81
|
+
TTo extends string = '',
|
|
82
|
+
> = ToOptions<RegisteredRoutesInfo, TFrom, TTo> & MatchRouteOptions
|
|
83
|
+
|
|
84
|
+
export type MakeMatchRouteOptions<
|
|
85
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
86
|
+
TTo extends string = '',
|
|
87
|
+
> = ToOptions<RegisteredRoutesInfo, TFrom, TTo> &
|
|
88
|
+
MatchRouteOptions & {
|
|
89
|
+
// 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
|
|
90
|
+
children?:
|
|
91
|
+
| ReactNode
|
|
92
|
+
| ((
|
|
93
|
+
params: RouteByPath<
|
|
94
|
+
RegisteredRoutesInfo,
|
|
95
|
+
ResolveRelativePath<TFrom, NoInfer<TTo>>
|
|
96
|
+
>['__types']['allParams'],
|
|
97
|
+
) => ReactNode)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type MakeLinkPropsOptions<
|
|
101
|
+
TFrom extends ValidFromPath<RegisteredRoutesInfo> = '/',
|
|
102
|
+
TTo extends string = '',
|
|
103
|
+
> = LinkPropsOptions<TFrom, TTo> & React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
104
|
+
|
|
105
|
+
export type MakeLinkOptions<
|
|
106
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
107
|
+
TTo extends string = '',
|
|
108
|
+
> = LinkPropsOptions<TFrom, TTo> &
|
|
109
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement> &
|
|
110
|
+
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
|
|
111
|
+
// 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
|
|
112
|
+
children?: ReactNode | ((state: { isActive: boolean }) => ReactNode)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
declare module '@tanstack/router' {
|
|
116
|
+
interface FrameworkGenerics {
|
|
117
|
+
Component: RouteComponent
|
|
118
|
+
ErrorComponent: RouteComponent<{
|
|
119
|
+
error: Error
|
|
120
|
+
info: { componentStack: string }
|
|
121
|
+
}>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface RouterOptions<TRouteTree> {
|
|
125
|
+
// ssrFooter?: () => JSX.Element | Node
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface FrameworkRouteOptions {
|
|
129
|
+
wrapInSuspense?: boolean
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
149
133
|
export type PromptProps = {
|
|
150
134
|
message: string
|
|
151
|
-
|
|
152
|
-
children?:
|
|
135
|
+
condition?: boolean | any
|
|
136
|
+
children?: ReactNode
|
|
153
137
|
}
|
|
154
138
|
|
|
155
139
|
//
|
|
156
140
|
|
|
157
|
-
|
|
158
|
-
|
|
141
|
+
export function useLinkProps<
|
|
142
|
+
TFrom extends ValidFromPath<RegisteredRoutesInfo> = '/',
|
|
143
|
+
TTo extends string = '',
|
|
144
|
+
>(
|
|
145
|
+
options: MakeLinkPropsOptions<TFrom, TTo>,
|
|
146
|
+
): React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
147
|
+
const router = useRouterContext()
|
|
148
|
+
|
|
149
|
+
const {
|
|
150
|
+
// custom props
|
|
151
|
+
type,
|
|
152
|
+
children,
|
|
153
|
+
target,
|
|
154
|
+
activeProps = () => ({ className: 'active' }),
|
|
155
|
+
inactiveProps = () => ({}),
|
|
156
|
+
activeOptions,
|
|
157
|
+
disabled,
|
|
158
|
+
// fromCurrent,
|
|
159
|
+
hash,
|
|
160
|
+
search,
|
|
161
|
+
params,
|
|
162
|
+
to = '.',
|
|
163
|
+
preload,
|
|
164
|
+
preloadDelay,
|
|
165
|
+
replace,
|
|
166
|
+
// element props
|
|
167
|
+
style,
|
|
168
|
+
className,
|
|
169
|
+
onClick,
|
|
170
|
+
onFocus,
|
|
171
|
+
onMouseEnter,
|
|
172
|
+
onMouseLeave,
|
|
173
|
+
onTouchStart,
|
|
174
|
+
...rest
|
|
175
|
+
} = options
|
|
176
|
+
|
|
177
|
+
const linkInfo = router.buildLink(options as any)
|
|
178
|
+
|
|
179
|
+
if (linkInfo.type === 'external') {
|
|
180
|
+
const { href } = linkInfo
|
|
181
|
+
return { href }
|
|
182
|
+
}
|
|
159
183
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
const {
|
|
185
|
+
handleClick,
|
|
186
|
+
handleFocus,
|
|
187
|
+
handleEnter,
|
|
188
|
+
handleLeave,
|
|
189
|
+
handleTouchStart,
|
|
190
|
+
isActive,
|
|
191
|
+
next,
|
|
192
|
+
} = linkInfo
|
|
193
|
+
|
|
194
|
+
const reactHandleClick = (e: Event) => {
|
|
195
|
+
if (React.startTransition) {
|
|
196
|
+
// This is a hack for react < 18
|
|
197
|
+
React.startTransition(() => {
|
|
198
|
+
handleClick(e)
|
|
199
|
+
})
|
|
200
|
+
} else {
|
|
201
|
+
handleClick(e)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
166
204
|
|
|
167
|
-
const
|
|
205
|
+
const composeHandlers =
|
|
206
|
+
(handlers: (undefined | ((e: any) => void))[]) =>
|
|
207
|
+
(e: React.SyntheticEvent) => {
|
|
208
|
+
if (e.persist) e.persist()
|
|
209
|
+
handlers.filter(Boolean).forEach((handler) => {
|
|
210
|
+
if (e.defaultPrevented) return
|
|
211
|
+
handler!(e)
|
|
212
|
+
})
|
|
213
|
+
}
|
|
168
214
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
215
|
+
// Get the active props
|
|
216
|
+
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
|
|
217
|
+
? functionalUpdate(activeProps as any, {}) ?? {}
|
|
218
|
+
: {}
|
|
219
|
+
|
|
220
|
+
// Get the inactive props
|
|
221
|
+
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
222
|
+
isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
...resolvedActiveProps,
|
|
226
|
+
...resolvedInactiveProps,
|
|
227
|
+
...rest,
|
|
228
|
+
href: disabled ? undefined : next.href,
|
|
229
|
+
onClick: composeHandlers([onClick, reactHandleClick]),
|
|
230
|
+
onFocus: composeHandlers([onFocus, handleFocus]),
|
|
231
|
+
onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
|
|
232
|
+
onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
|
|
233
|
+
onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
|
|
234
|
+
target,
|
|
235
|
+
style: {
|
|
236
|
+
...style,
|
|
237
|
+
...resolvedActiveProps.style,
|
|
238
|
+
...resolvedInactiveProps.style,
|
|
239
|
+
},
|
|
240
|
+
className:
|
|
241
|
+
[
|
|
242
|
+
className,
|
|
243
|
+
resolvedActiveProps.className,
|
|
244
|
+
resolvedInactiveProps.className,
|
|
245
|
+
]
|
|
246
|
+
.filter(Boolean)
|
|
247
|
+
.join(' ') || undefined,
|
|
248
|
+
...(disabled
|
|
249
|
+
? {
|
|
250
|
+
role: 'link',
|
|
251
|
+
'aria-disabled': true,
|
|
252
|
+
}
|
|
253
|
+
: undefined),
|
|
254
|
+
['data-status']: isActive ? 'active' : undefined,
|
|
255
|
+
}
|
|
172
256
|
}
|
|
173
257
|
|
|
174
|
-
export
|
|
175
|
-
|
|
258
|
+
export interface LinkFn<
|
|
259
|
+
TDefaultFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
260
|
+
TDefaultTo extends string = '',
|
|
261
|
+
> {
|
|
262
|
+
<
|
|
263
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = TDefaultFrom,
|
|
264
|
+
TTo extends string = TDefaultTo,
|
|
265
|
+
>(
|
|
266
|
+
props: MakeLinkOptions<TFrom, TTo> & React.RefAttributes<HTMLAnchorElement>,
|
|
267
|
+
): ReactNode
|
|
176
268
|
}
|
|
177
269
|
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
270
|
+
export const Link: LinkFn = React.forwardRef((props: any, ref) => {
|
|
271
|
+
const linkProps = useLinkProps(props)
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<a
|
|
275
|
+
{...{
|
|
276
|
+
ref: ref as any,
|
|
277
|
+
...linkProps,
|
|
278
|
+
children:
|
|
279
|
+
typeof props.children === 'function'
|
|
280
|
+
? props.children({
|
|
281
|
+
isActive: (linkProps as any)['data-status'] === 'active',
|
|
282
|
+
})
|
|
283
|
+
: props.children,
|
|
284
|
+
}}
|
|
285
|
+
/>
|
|
183
286
|
)
|
|
184
|
-
}
|
|
287
|
+
}) as any
|
|
185
288
|
|
|
186
|
-
export function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
router: Router<any, any>,
|
|
192
|
-
): Pick<AnyRoute, 'useRoute' | 'linkProps' | 'Link' | 'MatchRoute'> => {
|
|
193
|
-
return {
|
|
194
|
-
useRoute: (subRouteId = '.' as any) => {
|
|
195
|
-
const resolvedRouteId = router.resolvePath(
|
|
196
|
-
route.routeId,
|
|
197
|
-
subRouteId as string,
|
|
198
|
-
)
|
|
199
|
-
const resolvedRoute = router.getRoute(resolvedRouteId)
|
|
200
|
-
useRouterSubscription(router)
|
|
201
|
-
invariant(
|
|
202
|
-
resolvedRoute,
|
|
203
|
-
`Could not find a route for route "${
|
|
204
|
-
resolvedRouteId as string
|
|
205
|
-
}"! Did you forget to add it to your route config?`,
|
|
206
|
-
)
|
|
207
|
-
return resolvedRoute
|
|
208
|
-
},
|
|
209
|
-
linkProps: (options) => {
|
|
210
|
-
const {
|
|
211
|
-
// custom props
|
|
212
|
-
type,
|
|
213
|
-
children,
|
|
214
|
-
target,
|
|
215
|
-
activeProps = () => ({ className: 'active' }),
|
|
216
|
-
inactiveProps = () => ({}),
|
|
217
|
-
activeOptions,
|
|
218
|
-
disabled,
|
|
219
|
-
// fromCurrent,
|
|
220
|
-
hash,
|
|
221
|
-
search,
|
|
222
|
-
params,
|
|
223
|
-
to,
|
|
224
|
-
preload,
|
|
225
|
-
preloadDelay,
|
|
226
|
-
preloadMaxAge,
|
|
227
|
-
replace,
|
|
228
|
-
// element props
|
|
229
|
-
style,
|
|
230
|
-
className,
|
|
231
|
-
onClick,
|
|
232
|
-
onFocus,
|
|
233
|
-
onMouseEnter,
|
|
234
|
-
onMouseLeave,
|
|
235
|
-
onTouchStart,
|
|
236
|
-
onTouchEnd,
|
|
237
|
-
...rest
|
|
238
|
-
} = options
|
|
239
|
-
|
|
240
|
-
const linkInfo = route.buildLink(options)
|
|
241
|
-
|
|
242
|
-
if (linkInfo.type === 'external') {
|
|
243
|
-
const { href } = linkInfo
|
|
244
|
-
return { href }
|
|
245
|
-
}
|
|
289
|
+
export function Navigate<
|
|
290
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
291
|
+
TTo extends string = '',
|
|
292
|
+
>(props: NavigateOptions<RegisteredRoutesInfo, TFrom, TTo>): null {
|
|
293
|
+
const router = useRouterContext()
|
|
246
294
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
handleEnter,
|
|
251
|
-
handleLeave,
|
|
252
|
-
isActive,
|
|
253
|
-
next,
|
|
254
|
-
} = linkInfo
|
|
255
|
-
|
|
256
|
-
const composeHandlers =
|
|
257
|
-
(handlers: (undefined | ((e: any) => void))[]) =>
|
|
258
|
-
(e: React.SyntheticEvent) => {
|
|
259
|
-
e.persist()
|
|
260
|
-
handlers.forEach((handler) => {
|
|
261
|
-
if (handler) handler(e)
|
|
262
|
-
})
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Get the active props
|
|
266
|
-
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
267
|
-
isActive ? functionalUpdate(activeProps, {}) ?? {} : {}
|
|
268
|
-
|
|
269
|
-
// Get the inactive props
|
|
270
|
-
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
271
|
-
isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
...resolvedActiveProps,
|
|
275
|
-
...resolvedInactiveProps,
|
|
276
|
-
...rest,
|
|
277
|
-
href: disabled ? undefined : next.href,
|
|
278
|
-
onClick: composeHandlers([handleClick, onClick]),
|
|
279
|
-
onFocus: composeHandlers([handleFocus, onFocus]),
|
|
280
|
-
onMouseEnter: composeHandlers([handleEnter, onMouseEnter]),
|
|
281
|
-
onMouseLeave: composeHandlers([handleLeave, onMouseLeave]),
|
|
282
|
-
target,
|
|
283
|
-
style: {
|
|
284
|
-
...style,
|
|
285
|
-
...resolvedActiveProps.style,
|
|
286
|
-
...resolvedInactiveProps.style,
|
|
287
|
-
},
|
|
288
|
-
className:
|
|
289
|
-
[
|
|
290
|
-
className,
|
|
291
|
-
resolvedActiveProps.className,
|
|
292
|
-
resolvedInactiveProps.className,
|
|
293
|
-
]
|
|
294
|
-
.filter(Boolean)
|
|
295
|
-
.join(' ') || undefined,
|
|
296
|
-
...(disabled
|
|
297
|
-
? {
|
|
298
|
-
role: 'link',
|
|
299
|
-
'aria-disabled': true,
|
|
300
|
-
}
|
|
301
|
-
: undefined),
|
|
302
|
-
['data-status']: isActive ? 'active' : undefined,
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
Link: React.forwardRef((props: any, ref) => {
|
|
306
|
-
const linkProps = route.linkProps(props)
|
|
307
|
-
|
|
308
|
-
useRouterSubscription(router)
|
|
309
|
-
|
|
310
|
-
return (
|
|
311
|
-
<a
|
|
312
|
-
{...{
|
|
313
|
-
ref: ref as any,
|
|
314
|
-
...linkProps,
|
|
315
|
-
children:
|
|
316
|
-
typeof props.children === 'function'
|
|
317
|
-
? props.children({
|
|
318
|
-
isActive: (linkProps as any)['data-status'] === 'active',
|
|
319
|
-
})
|
|
320
|
-
: props.children,
|
|
321
|
-
}}
|
|
322
|
-
/>
|
|
323
|
-
)
|
|
324
|
-
}) as any,
|
|
325
|
-
MatchRoute: (opts) => {
|
|
326
|
-
const { pending, caseSensitive, children, ...rest } = opts
|
|
327
|
-
|
|
328
|
-
const params = route.matchRoute(rest as any, {
|
|
329
|
-
pending,
|
|
330
|
-
caseSensitive,
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
if (!params) {
|
|
334
|
-
return null
|
|
335
|
-
}
|
|
295
|
+
React.useLayoutEffect(() => {
|
|
296
|
+
router.navigate(props as any)
|
|
297
|
+
}, [])
|
|
336
298
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
: (opts.children as any)
|
|
340
|
-
},
|
|
341
|
-
}
|
|
342
|
-
}
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
343
301
|
|
|
344
|
-
|
|
345
|
-
...opts,
|
|
346
|
-
createRouter: (router) => {
|
|
347
|
-
const routerExt: Pick<Router<any, any>, 'useMatch' | 'useState'> = {
|
|
348
|
-
useState: () => {
|
|
349
|
-
useRouterSubscription(router)
|
|
350
|
-
return router.state
|
|
351
|
-
},
|
|
352
|
-
useMatch: (routeId) => {
|
|
353
|
-
useRouterSubscription(router)
|
|
354
|
-
|
|
355
|
-
invariant(
|
|
356
|
-
routeId !== rootRouteId,
|
|
357
|
-
`"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
const runtimeMatch = useMatch()
|
|
361
|
-
const match = router.state.matches.find((d) => d.routeId === routeId)
|
|
362
|
-
|
|
363
|
-
invariant(
|
|
364
|
-
match,
|
|
365
|
-
`Could not find a match for route "${
|
|
366
|
-
routeId as string
|
|
367
|
-
}" being rendered in this component!`,
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
invariant(
|
|
371
|
-
runtimeMatch.routeId == match?.routeId,
|
|
372
|
-
`useMatch('${
|
|
373
|
-
match?.routeId as string
|
|
374
|
-
}') is being called in a component that is meant to render the '${
|
|
375
|
-
runtimeMatch.routeId
|
|
376
|
-
}' route. Did you mean to 'useRoute(${
|
|
377
|
-
match?.routeId as string
|
|
378
|
-
})' instead?`,
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
if (!match) {
|
|
382
|
-
invariant('Match not found!')
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return match
|
|
386
|
-
},
|
|
387
|
-
}
|
|
302
|
+
type MatchesContextValue = AnyRouteMatch[]
|
|
388
303
|
|
|
389
|
-
|
|
304
|
+
export const matchesContext = React.createContext<MatchesContextValue>(null!)
|
|
305
|
+
export const routerContext = React.createContext<{ router: RegisteredRouter }>(
|
|
306
|
+
null!,
|
|
307
|
+
)
|
|
390
308
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
309
|
+
export type MatchesProviderProps = {
|
|
310
|
+
value: MatchesContextValue
|
|
311
|
+
children: ReactNode
|
|
312
|
+
}
|
|
395
313
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return res
|
|
314
|
+
export class ReactRouter<
|
|
315
|
+
TRouteConfig extends AnyRootRoute = RootRoute,
|
|
316
|
+
TRoutesInfo extends AnyRoutesInfo = RoutesInfo<TRouteConfig>,
|
|
317
|
+
> extends Router<TRouteConfig, TRoutesInfo> {
|
|
318
|
+
constructor(opts: RouterConstructorOptions<TRouteConfig>) {
|
|
319
|
+
super({
|
|
320
|
+
...opts,
|
|
321
|
+
loadComponent: async (component) => {
|
|
322
|
+
if (component.preload) {
|
|
323
|
+
await component.preload()
|
|
407
324
|
}
|
|
408
|
-
}
|
|
409
325
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return coreRouter as any
|
|
326
|
+
return component as any
|
|
327
|
+
},
|
|
328
|
+
})
|
|
329
|
+
}
|
|
415
330
|
}
|
|
416
331
|
|
|
417
332
|
export type RouterProps<
|
|
418
|
-
TRouteConfig extends
|
|
419
|
-
|
|
333
|
+
TRouteConfig extends AnyRootRoute = RootRoute,
|
|
334
|
+
TRoutesInfo extends AnyRoutesInfo = DefaultRoutesInfo,
|
|
420
335
|
> = RouterOptions<TRouteConfig> & {
|
|
421
|
-
router: Router<TRouteConfig,
|
|
422
|
-
// Children will default to `<Outlet />` if not provided
|
|
423
|
-
children?: React.ReactNode
|
|
336
|
+
router: Router<TRouteConfig, TRoutesInfo>
|
|
424
337
|
}
|
|
425
338
|
|
|
426
339
|
export function RouterProvider<
|
|
427
|
-
TRouteConfig extends
|
|
428
|
-
|
|
429
|
-
>({
|
|
340
|
+
TRouteConfig extends AnyRootRoute = RootRoute,
|
|
341
|
+
TRoutesInfo extends AnyRoutesInfo = DefaultRoutesInfo,
|
|
342
|
+
>({ router, ...rest }: RouterProps<TRouteConfig, TRoutesInfo>) {
|
|
430
343
|
router.update(rest)
|
|
431
344
|
|
|
432
|
-
|
|
345
|
+
const currentMatches = useStore(router.__store, (s) => s.currentMatches)
|
|
433
346
|
|
|
434
|
-
|
|
435
|
-
return router.mount()
|
|
436
|
-
}, [router])
|
|
347
|
+
React.useEffect(router.mount, [router])
|
|
437
348
|
|
|
438
349
|
return (
|
|
439
|
-
<routerContext.Provider value={{ router }}>
|
|
440
|
-
<
|
|
441
|
-
|
|
442
|
-
|
|
350
|
+
<routerContext.Provider value={{ router: router as any }}>
|
|
351
|
+
<matchesContext.Provider value={[undefined!, ...currentMatches]}>
|
|
352
|
+
<CatchBoundary
|
|
353
|
+
errorComponent={ErrorComponent}
|
|
354
|
+
onCatch={() => {
|
|
355
|
+
warning(
|
|
356
|
+
false,
|
|
357
|
+
`Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`,
|
|
358
|
+
)
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
<Outlet />
|
|
362
|
+
</CatchBoundary>
|
|
363
|
+
</matchesContext.Provider>
|
|
443
364
|
</routerContext.Provider>
|
|
444
365
|
)
|
|
445
366
|
}
|
|
446
367
|
|
|
447
|
-
function
|
|
368
|
+
export function useRouterContext(): RegisteredRouter {
|
|
448
369
|
const value = React.useContext(routerContext)
|
|
449
|
-
warning(
|
|
370
|
+
warning(value, 'useRouter must be used inside a <Router> component!')
|
|
450
371
|
|
|
451
|
-
|
|
372
|
+
useStore(value.router.__store)
|
|
452
373
|
|
|
453
|
-
return value.router
|
|
374
|
+
return value.router
|
|
454
375
|
}
|
|
455
376
|
|
|
456
|
-
function
|
|
377
|
+
export function useRouter<T = RouterStore>(
|
|
378
|
+
track?: (state: Router['__store']) => T,
|
|
379
|
+
shallow?: boolean,
|
|
380
|
+
): RegisteredRouter {
|
|
381
|
+
const router = useRouterContext()
|
|
382
|
+
useStore(router.__store, track as any, shallow)
|
|
383
|
+
return router
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function useMatches(): RouteMatch[] {
|
|
457
387
|
return React.useContext(matchesContext)
|
|
458
388
|
}
|
|
459
389
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
390
|
+
export function useMatch<
|
|
391
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'],
|
|
392
|
+
TStrict extends boolean = true,
|
|
393
|
+
TRouteMatch = RouteMatch<
|
|
394
|
+
RegisteredRoutesInfo,
|
|
395
|
+
RegisteredRoutesInfo['routesById'][TFrom]
|
|
396
|
+
>,
|
|
397
|
+
>(opts?: {
|
|
398
|
+
from: TFrom
|
|
399
|
+
strict?: TStrict
|
|
400
|
+
track?: (match: TRouteMatch) => any
|
|
401
|
+
shallow?: boolean
|
|
402
|
+
}): TStrict extends true ? TRouteMatch : TRouteMatch | undefined {
|
|
403
|
+
const router = useRouterContext()
|
|
404
|
+
const nearestMatch = useMatches()[0]!
|
|
405
|
+
const match = opts?.from
|
|
406
|
+
? router.state.currentMatches.find((d) => d.route.id === opts?.from)
|
|
407
|
+
: nearestMatch
|
|
408
|
+
|
|
409
|
+
invariant(
|
|
410
|
+
match,
|
|
411
|
+
`Could not find ${
|
|
412
|
+
opts?.from ? `an active match from "${opts.from}"` : 'a nearest match!'
|
|
413
|
+
}`,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if (opts?.strict ?? true) {
|
|
417
|
+
invariant(
|
|
418
|
+
nearestMatch.route.id == match?.route.id,
|
|
419
|
+
`useMatch("${
|
|
420
|
+
match?.route.id as string
|
|
421
|
+
}") is being called in a component that is meant to render the '${
|
|
422
|
+
nearestMatch.route.id
|
|
423
|
+
}' route. Did you mean to 'useMatch("${
|
|
424
|
+
match?.route.id as string
|
|
425
|
+
}", { strict: false })' or 'useRoute("${
|
|
426
|
+
match?.route.id as string
|
|
427
|
+
}")' instead?`,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
useStore(
|
|
432
|
+
match!.__store as any,
|
|
433
|
+
(d) => opts?.track?.(match as any) ?? match,
|
|
434
|
+
opts?.shallow,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
return match as any
|
|
472
438
|
}
|
|
473
439
|
|
|
474
|
-
export function
|
|
475
|
-
|
|
476
|
-
|
|
440
|
+
export function useRoute<
|
|
441
|
+
TId extends keyof RegisteredRoutesInfo['routesById'] = '/',
|
|
442
|
+
>(routeId: TId): RegisteredRoutesInfo['routesById'][TId] {
|
|
443
|
+
const router = useRouterContext()
|
|
444
|
+
const resolvedRoute = router.getRoute(routeId as any)
|
|
445
|
+
|
|
446
|
+
invariant(
|
|
447
|
+
resolvedRoute,
|
|
448
|
+
`Could not find a route for route "${
|
|
449
|
+
routeId as string
|
|
450
|
+
}"! Did you forget to add it to your route?`,
|
|
451
|
+
)
|
|
477
452
|
|
|
478
|
-
|
|
453
|
+
return resolvedRoute as any
|
|
454
|
+
}
|
|
479
455
|
|
|
480
|
-
|
|
456
|
+
export function useSearch<
|
|
457
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'],
|
|
458
|
+
TStrict extends boolean = true,
|
|
459
|
+
TSearch = RegisteredRoutesInfo['routesById'][TFrom]['__types']['fullSearchSchema'],
|
|
460
|
+
TSelected = TSearch,
|
|
461
|
+
>(opts?: {
|
|
462
|
+
from: TFrom
|
|
463
|
+
strict?: TStrict
|
|
464
|
+
track?: (search: TSearch) => TSelected
|
|
465
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
466
|
+
const { track, ...matchOpts } = opts as any
|
|
467
|
+
const match = useMatch(matchOpts)
|
|
468
|
+
useStore(match.__store, (d: any) => opts?.track?.(d.search) ?? d.search, true)
|
|
469
|
+
|
|
470
|
+
return (match as unknown as RouteMatch).state.search as any
|
|
471
|
+
}
|
|
481
472
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
473
|
+
export function useParams<
|
|
474
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'] = '/',
|
|
475
|
+
TDefaultSelected = RegisteredRoutesInfo['allParams'] &
|
|
476
|
+
RegisteredRoutesInfo['routesById'][TFrom]['__types']['allParams'],
|
|
477
|
+
TSelected = TDefaultSelected,
|
|
478
|
+
>(opts?: {
|
|
479
|
+
from: TFrom
|
|
480
|
+
track?: (search: TDefaultSelected) => TSelected
|
|
481
|
+
}): TSelected {
|
|
482
|
+
const router = useRouterContext()
|
|
483
|
+
useStore(
|
|
484
|
+
router.__store,
|
|
485
|
+
(d) => {
|
|
486
|
+
const params = last(d.currentMatches)?.params as any
|
|
487
|
+
return opts?.track?.(params) ?? params
|
|
488
|
+
},
|
|
489
|
+
true,
|
|
490
|
+
)
|
|
486
491
|
|
|
487
|
-
|
|
488
|
-
|
|
492
|
+
return last(router.state.currentMatches)?.params as any
|
|
493
|
+
}
|
|
489
494
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
495
|
+
export function useNavigate<
|
|
496
|
+
TDefaultFrom extends keyof RegisteredRoutesInfo['routesById'] = '/',
|
|
497
|
+
>(defaultOpts?: { from?: TDefaultFrom }) {
|
|
498
|
+
const router = useRouterContext()
|
|
499
|
+
return React.useCallback(
|
|
500
|
+
<
|
|
501
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'] = TDefaultFrom,
|
|
502
|
+
TTo extends string = '',
|
|
503
|
+
>(
|
|
504
|
+
opts?: MakeLinkOptions<TFrom, TTo>,
|
|
505
|
+
) => {
|
|
506
|
+
return router.navigate({ ...defaultOpts, ...(opts as any) })
|
|
507
|
+
},
|
|
508
|
+
[],
|
|
509
|
+
)
|
|
510
|
+
}
|
|
494
511
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
router.options.useErrorBoundary
|
|
498
|
-
) {
|
|
499
|
-
throw childMatch.error
|
|
500
|
-
}
|
|
512
|
+
export function useMatchRoute() {
|
|
513
|
+
const router = useRouterContext()
|
|
501
514
|
|
|
502
|
-
|
|
503
|
-
|
|
515
|
+
return React.useCallback(
|
|
516
|
+
<
|
|
517
|
+
TFrom extends ValidFromPath<RegisteredRoutesInfo> = '/',
|
|
518
|
+
TTo extends string = '',
|
|
519
|
+
>(
|
|
520
|
+
opts: MakeUseMatchRouteOptions<TFrom, TTo>,
|
|
521
|
+
) => {
|
|
522
|
+
const { pending, caseSensitive, ...rest } = opts
|
|
523
|
+
|
|
524
|
+
return router.matchRoute(rest as any, {
|
|
525
|
+
pending,
|
|
526
|
+
caseSensitive,
|
|
527
|
+
})
|
|
528
|
+
},
|
|
529
|
+
[],
|
|
530
|
+
)
|
|
531
|
+
}
|
|
504
532
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
533
|
+
export function MatchRoute<
|
|
534
|
+
TFrom extends ValidFromPath<RegisteredRoutesInfo> = '/',
|
|
535
|
+
TTo extends string = '',
|
|
536
|
+
>(props: MakeMatchRouteOptions<TFrom, TTo>): any {
|
|
537
|
+
const matchRoute = useMatchRoute()
|
|
538
|
+
const params = matchRoute(props)
|
|
509
539
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
540
|
+
if (!params) {
|
|
541
|
+
return null
|
|
542
|
+
}
|
|
514
543
|
|
|
515
|
-
|
|
516
|
-
|
|
544
|
+
if (typeof props.children === 'function') {
|
|
545
|
+
return (props.children as any)(params)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return params ? props.children : null
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function Outlet() {
|
|
552
|
+
const matches = useMatches().slice(1)
|
|
553
|
+
const match = matches[0]
|
|
554
|
+
|
|
555
|
+
if (!match) {
|
|
556
|
+
return null
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return <SubOutlet matches={matches} match={match} />
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function SubOutlet({
|
|
563
|
+
matches,
|
|
564
|
+
match,
|
|
565
|
+
}: {
|
|
566
|
+
matches: RouteMatch[]
|
|
567
|
+
match: RouteMatch
|
|
568
|
+
}) {
|
|
569
|
+
const router = useRouterContext()
|
|
570
|
+
useStore(match!.__store, (store) => [store.status, store.error], true)
|
|
571
|
+
|
|
572
|
+
const defaultPending = React.useCallback(() => null, [])
|
|
517
573
|
|
|
518
|
-
|
|
519
|
-
|
|
574
|
+
const PendingComponent = (match.pendingComponent ??
|
|
575
|
+
router.options.defaultPendingComponent ??
|
|
576
|
+
defaultPending) as any
|
|
520
577
|
|
|
521
|
-
const
|
|
522
|
-
|
|
578
|
+
const errorComponent =
|
|
579
|
+
match.errorComponent ?? router.options.defaultErrorComponent
|
|
580
|
+
|
|
581
|
+
const ResolvedSuspenseBoundary =
|
|
582
|
+
match.route.options.wrapInSuspense ?? true ? React.Suspense : SafeFragment
|
|
583
|
+
const ResolvedCatchBoundary = errorComponent ? CatchBoundary : SafeFragment
|
|
523
584
|
|
|
524
585
|
return (
|
|
525
|
-
<
|
|
526
|
-
<
|
|
527
|
-
|
|
586
|
+
<matchesContext.Provider value={matches}>
|
|
587
|
+
<ResolvedSuspenseBoundary fallback={<PendingComponent />}>
|
|
588
|
+
<ResolvedCatchBoundary
|
|
589
|
+
key={match.route.id}
|
|
590
|
+
errorComponent={errorComponent}
|
|
591
|
+
onCatch={() => {
|
|
592
|
+
warning(false, `Error in route match: ${match.id}`)
|
|
593
|
+
}}
|
|
594
|
+
>
|
|
595
|
+
<Inner match={match} />
|
|
596
|
+
</ResolvedCatchBoundary>
|
|
597
|
+
</ResolvedSuspenseBoundary>
|
|
598
|
+
</matchesContext.Provider>
|
|
528
599
|
)
|
|
529
600
|
}
|
|
530
601
|
|
|
602
|
+
function Inner(props: { match: RouteMatch }): any {
|
|
603
|
+
const router = useRouterContext()
|
|
604
|
+
|
|
605
|
+
if (props.match.state.status === 'error') {
|
|
606
|
+
throw props.match.state.error
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (props.match.state.status === 'success') {
|
|
610
|
+
return React.createElement(
|
|
611
|
+
(props.match.component as any) ??
|
|
612
|
+
router.options.defaultComponent ??
|
|
613
|
+
Outlet,
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (props.match.state.status === 'pending') {
|
|
618
|
+
throw props.match.__loadPromise
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
invariant(
|
|
622
|
+
false,
|
|
623
|
+
'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
|
|
624
|
+
)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function SafeFragment(props: any) {
|
|
628
|
+
return <>{props.children}</>
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// This is the messiest thing ever... I'm either seriously tired (likely) or
|
|
632
|
+
// there has to be a better way to reset error boundaries when the
|
|
633
|
+
// router's location key changes.
|
|
634
|
+
|
|
531
635
|
class CatchBoundary extends React.Component<{
|
|
532
636
|
children: any
|
|
533
|
-
|
|
637
|
+
errorComponent: any
|
|
638
|
+
onCatch: (error: any, info: any) => void
|
|
534
639
|
}> {
|
|
535
640
|
state = {
|
|
536
641
|
error: false,
|
|
642
|
+
info: undefined,
|
|
537
643
|
}
|
|
538
644
|
componentDidCatch(error: any, info: any) {
|
|
645
|
+
this.props.onCatch(error, info)
|
|
539
646
|
console.error(error)
|
|
540
|
-
|
|
541
647
|
this.setState({
|
|
542
648
|
error,
|
|
543
649
|
info,
|
|
544
650
|
})
|
|
545
651
|
}
|
|
546
652
|
render() {
|
|
547
|
-
|
|
653
|
+
return (
|
|
654
|
+
<CatchBoundaryInner
|
|
655
|
+
{...this.props}
|
|
656
|
+
errorState={this.state}
|
|
657
|
+
reset={() => this.setState({})}
|
|
658
|
+
/>
|
|
659
|
+
)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
548
662
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
663
|
+
function CatchBoundaryInner(props: {
|
|
664
|
+
children: any
|
|
665
|
+
errorComponent: any
|
|
666
|
+
errorState: { error: unknown; info: any }
|
|
667
|
+
reset: () => void
|
|
668
|
+
}) {
|
|
669
|
+
const [activeErrorState, setActiveErrorState] = React.useState(
|
|
670
|
+
props.errorState,
|
|
671
|
+
)
|
|
672
|
+
const router = useRouterContext()
|
|
673
|
+
const errorComponent = props.errorComponent ?? ErrorComponent
|
|
674
|
+
const prevKeyRef = React.useRef('' as any)
|
|
675
|
+
|
|
676
|
+
React.useEffect(() => {
|
|
677
|
+
if (activeErrorState) {
|
|
678
|
+
if (router.state.currentLocation.key !== prevKeyRef.current) {
|
|
679
|
+
setActiveErrorState({} as any)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
prevKeyRef.current = router.state.currentLocation.key
|
|
684
|
+
}, [activeErrorState, router.state.currentLocation.key])
|
|
685
|
+
|
|
686
|
+
React.useEffect(() => {
|
|
687
|
+
if (props.errorState.error) {
|
|
688
|
+
setActiveErrorState(props.errorState)
|
|
553
689
|
}
|
|
690
|
+
// props.reset()
|
|
691
|
+
}, [props.errorState.error])
|
|
554
692
|
|
|
555
|
-
|
|
693
|
+
if (props.errorState.error && activeErrorState.error) {
|
|
694
|
+
return React.createElement(errorComponent, activeErrorState)
|
|
556
695
|
}
|
|
696
|
+
|
|
697
|
+
return props.children
|
|
557
698
|
}
|
|
558
699
|
|
|
559
|
-
export function
|
|
700
|
+
export function ErrorComponent({ error }: { error: any }) {
|
|
560
701
|
return (
|
|
561
702
|
<div style={{ padding: '.5rem', maxWidth: '100%' }}>
|
|
562
703
|
<strong style={{ fontSize: '1.2rem' }}>Something went wrong!</strong>
|
|
563
704
|
<div style={{ height: '.5rem' }} />
|
|
564
705
|
<div>
|
|
565
|
-
<pre
|
|
566
|
-
{
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{error.message}
|
|
577
|
-
</code>
|
|
578
|
-
) : null}
|
|
706
|
+
<pre
|
|
707
|
+
style={{
|
|
708
|
+
fontSize: '.7em',
|
|
709
|
+
border: '1px solid red',
|
|
710
|
+
borderRadius: '.25rem',
|
|
711
|
+
padding: '.5rem',
|
|
712
|
+
color: 'red',
|
|
713
|
+
overflow: 'auto',
|
|
714
|
+
}}
|
|
715
|
+
>
|
|
716
|
+
{error.message ? <code>{error.message}</code> : null}
|
|
579
717
|
</pre>
|
|
580
718
|
</div>
|
|
581
|
-
<div style={{ height: '1rem' }} />
|
|
582
|
-
<div
|
|
583
|
-
style={{
|
|
584
|
-
fontSize: '.8em',
|
|
585
|
-
borderLeft: '3px solid rgba(127, 127, 127, 1)',
|
|
586
|
-
paddingLeft: '.5rem',
|
|
587
|
-
opacity: 0.5,
|
|
588
|
-
}}
|
|
589
|
-
>
|
|
590
|
-
If you are the owner of this website, it's highly recommended that you
|
|
591
|
-
configure your own custom Catch/Error boundaries for the router. You can
|
|
592
|
-
optionally configure a boundary for each route.
|
|
593
|
-
</div>
|
|
594
719
|
</div>
|
|
595
720
|
)
|
|
596
721
|
}
|
|
597
722
|
|
|
598
|
-
export function
|
|
723
|
+
export function useBlocker(
|
|
724
|
+
message: string,
|
|
725
|
+
condition: boolean | any = true,
|
|
726
|
+
): void {
|
|
599
727
|
const router = useRouter()
|
|
600
728
|
|
|
601
729
|
React.useEffect(() => {
|
|
602
|
-
if (!
|
|
730
|
+
if (!condition) return
|
|
603
731
|
|
|
604
|
-
let unblock = router.history.block((
|
|
732
|
+
let unblock = router.history.block((retry, cancel) => {
|
|
605
733
|
if (window.confirm(message)) {
|
|
606
734
|
unblock()
|
|
607
|
-
|
|
735
|
+
retry()
|
|
608
736
|
} else {
|
|
609
|
-
|
|
737
|
+
cancel()
|
|
610
738
|
}
|
|
611
739
|
})
|
|
612
740
|
|
|
613
741
|
return unblock
|
|
614
|
-
}
|
|
742
|
+
})
|
|
615
743
|
}
|
|
616
744
|
|
|
617
|
-
export function
|
|
618
|
-
|
|
619
|
-
return (children ?? null) as
|
|
745
|
+
export function Block({ message, condition, children }: PromptProps) {
|
|
746
|
+
useBlocker(message, condition)
|
|
747
|
+
return (children ?? null) as ReactNode
|
|
620
748
|
}
|