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