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