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