@tanstack/react-router 0.0.1-beta.3 → 0.0.1-beta.30
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 -5
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
- package/build/cjs/index.js +447 -0
- package/build/cjs/index.js.map +1 -0
- package/build/esm/index.js +201 -2658
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +115 -33
- package/build/types/index.d.ts +67 -36
- package/build/umd/index.development.js +570 -675
- 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 +4 -3
- package/src/index.tsx +355 -209
- 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 -2493
- package/build/cjs/router-core/build/esm/index.js.map +0 -1
package/src/index.tsx
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
AnyRoute,
|
|
7
7
|
CheckId,
|
|
8
8
|
rootRouteId,
|
|
9
|
-
|
|
9
|
+
Route,
|
|
10
|
+
RegisteredAllRouteInfo,
|
|
11
|
+
RegisteredRouter,
|
|
10
12
|
RouterState,
|
|
11
13
|
ToIdOption,
|
|
12
14
|
} from '@tanstack/router-core'
|
|
@@ -31,15 +33,113 @@ import {
|
|
|
31
33
|
NoInfer,
|
|
32
34
|
ToOptions,
|
|
33
35
|
invariant,
|
|
36
|
+
Router,
|
|
34
37
|
} from '@tanstack/router-core'
|
|
35
38
|
|
|
36
39
|
export * from '@tanstack/router-core'
|
|
37
40
|
|
|
41
|
+
export type SyncRouteComponent<TProps = {}> = (
|
|
42
|
+
props: TProps,
|
|
43
|
+
) => JSX.Element | React.ReactNode
|
|
44
|
+
|
|
45
|
+
export type RouteComponent<TProps = {}> = SyncRouteComponent<TProps> & {
|
|
46
|
+
preload?: () => Promise<SyncRouteComponent<TProps>>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function lazy(
|
|
50
|
+
importer: () => Promise<{ default: SyncRouteComponent }>,
|
|
51
|
+
): RouteComponent {
|
|
52
|
+
const lazyComp = React.lazy(importer as any)
|
|
53
|
+
let promise: Promise<SyncRouteComponent>
|
|
54
|
+
let resolvedComp: SyncRouteComponent
|
|
55
|
+
|
|
56
|
+
const forwardedComp = React.forwardRef((props, ref) => {
|
|
57
|
+
const resolvedCompRef = React.useRef(resolvedComp || lazyComp)
|
|
58
|
+
return React.createElement(
|
|
59
|
+
resolvedCompRef.current as any,
|
|
60
|
+
{ ...(ref ? { ref } : {}), ...props } as any,
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const finalComp = forwardedComp as unknown as RouteComponent
|
|
65
|
+
|
|
66
|
+
finalComp.preload = () => {
|
|
67
|
+
if (!promise) {
|
|
68
|
+
promise = importer().then((module) => {
|
|
69
|
+
resolvedComp = module.default
|
|
70
|
+
return resolvedComp
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return promise
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return finalComp
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type LinkPropsOptions<
|
|
81
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
82
|
+
TFrom extends ValidFromPath<TAllRouteInfo>,
|
|
83
|
+
TTo extends string,
|
|
84
|
+
> = LinkOptions<TAllRouteInfo, TFrom, TTo> & {
|
|
85
|
+
// 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)
|
|
86
|
+
activeProps?:
|
|
87
|
+
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
88
|
+
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
89
|
+
// A function that returns additional props for the `inactive` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
90
|
+
inactiveProps?:
|
|
91
|
+
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
92
|
+
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type MakeMatchRouteOptions<
|
|
96
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
97
|
+
TFrom extends ValidFromPath<TAllRouteInfo>,
|
|
98
|
+
TTo extends string,
|
|
99
|
+
> = ToOptions<TAllRouteInfo, TFrom, TTo> &
|
|
100
|
+
MatchRouteOptions & {
|
|
101
|
+
// 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
|
|
102
|
+
children?:
|
|
103
|
+
| React.ReactNode
|
|
104
|
+
| ((
|
|
105
|
+
params: RouteInfoByPath<
|
|
106
|
+
TAllRouteInfo,
|
|
107
|
+
ResolveRelativePath<TFrom, NoInfer<TTo>>
|
|
108
|
+
>['allParams'],
|
|
109
|
+
) => React.ReactNode)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type MakeLinkPropsOptions<
|
|
113
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
114
|
+
TFrom extends ValidFromPath<TAllRouteInfo>,
|
|
115
|
+
TTo extends string,
|
|
116
|
+
> = LinkPropsOptions<TAllRouteInfo, TFrom, TTo> &
|
|
117
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
118
|
+
|
|
119
|
+
type MakeLinkOptions<
|
|
120
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
121
|
+
TFrom extends ValidFromPath<TAllRouteInfo>,
|
|
122
|
+
TTo extends string,
|
|
123
|
+
> = LinkPropsOptions<TAllRouteInfo, TFrom, TTo> &
|
|
124
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement> &
|
|
125
|
+
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
|
|
126
|
+
// 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
|
|
127
|
+
children?:
|
|
128
|
+
| React.ReactNode
|
|
129
|
+
| ((state: { isActive: boolean }) => React.ReactNode)
|
|
130
|
+
}
|
|
131
|
+
|
|
38
132
|
declare module '@tanstack/router-core' {
|
|
39
133
|
interface FrameworkGenerics {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
134
|
+
Component: RouteComponent
|
|
135
|
+
ErrorComponent: RouteComponent<{
|
|
136
|
+
error: unknown
|
|
137
|
+
info: { componentStack: string }
|
|
138
|
+
}>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface RouterOptions<TRouteConfig, TRouterContext> {
|
|
142
|
+
// ssrFooter?: () => JSX.Element | React.ReactNode
|
|
43
143
|
}
|
|
44
144
|
|
|
45
145
|
interface Router<
|
|
@@ -50,36 +150,26 @@ declare module '@tanstack/router-core' {
|
|
|
50
150
|
useRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
51
151
|
routeId: TId,
|
|
52
152
|
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
53
|
-
|
|
153
|
+
useNearestMatch: () => RouteMatch<TAllRouteInfo, RouteInfo>
|
|
154
|
+
useMatch: <
|
|
155
|
+
TId extends keyof TAllRouteInfo['routeInfoById'],
|
|
156
|
+
TStrict extends boolean = true,
|
|
157
|
+
>(
|
|
54
158
|
routeId: TId,
|
|
55
|
-
|
|
159
|
+
opts?: { strict?: TStrict },
|
|
160
|
+
) => TStrict extends true
|
|
161
|
+
? RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
162
|
+
:
|
|
163
|
+
| RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
164
|
+
| undefined
|
|
56
165
|
linkProps: <TTo extends string = '.'>(
|
|
57
|
-
props:
|
|
58
|
-
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
166
|
+
props: MakeLinkPropsOptions<TAllRouteInfo, '/', TTo>,
|
|
59
167
|
) => React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
60
168
|
Link: <TTo extends string = '.'>(
|
|
61
|
-
props:
|
|
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
|
-
},
|
|
169
|
+
props: MakeLinkOptions<TAllRouteInfo, '/', TTo>,
|
|
69
170
|
) => JSX.Element
|
|
70
171
|
MatchRoute: <TTo extends string = '.'>(
|
|
71
|
-
props:
|
|
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
|
-
},
|
|
172
|
+
props: MakeMatchRouteOptions<TAllRouteInfo, '/', TTo>,
|
|
83
173
|
) => JSX.Element
|
|
84
174
|
}
|
|
85
175
|
|
|
@@ -99,53 +189,20 @@ declare module '@tanstack/router-core' {
|
|
|
99
189
|
TResolved,
|
|
100
190
|
ToIdOption<TAllRouteInfo, TRouteInfo['id'], TTo>
|
|
101
191
|
>,
|
|
192
|
+
opts?: { strict?: boolean },
|
|
102
193
|
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TResolved]>
|
|
103
194
|
linkProps: <TTo extends string = '.'>(
|
|
104
|
-
props:
|
|
105
|
-
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
195
|
+
props: MakeLinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>,
|
|
106
196
|
) => React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
107
197
|
Link: <TTo extends string = '.'>(
|
|
108
|
-
props:
|
|
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
|
-
},
|
|
198
|
+
props: MakeLinkOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>,
|
|
116
199
|
) => JSX.Element
|
|
117
200
|
MatchRoute: <TTo extends string = '.'>(
|
|
118
|
-
props:
|
|
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
|
-
},
|
|
201
|
+
props: MakeMatchRouteOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>,
|
|
130
202
|
) => JSX.Element
|
|
131
203
|
}
|
|
132
204
|
}
|
|
133
205
|
|
|
134
|
-
type LinkPropsOptions<
|
|
135
|
-
TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
|
|
136
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
137
|
-
TTo extends string = '.',
|
|
138
|
-
> = LinkOptions<TAllRouteInfo, TFrom, TTo> & {
|
|
139
|
-
// 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
|
-
activeProps?:
|
|
141
|
-
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
142
|
-
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
143
|
-
// A function that returns additional props for the `inactive` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
144
|
-
inactiveProps?:
|
|
145
|
-
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
146
|
-
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
206
|
export type PromptProps = {
|
|
150
207
|
message: string
|
|
151
208
|
when?: boolean | any
|
|
@@ -154,20 +211,22 @@ export type PromptProps = {
|
|
|
154
211
|
|
|
155
212
|
//
|
|
156
213
|
|
|
157
|
-
|
|
158
|
-
|
|
214
|
+
export function Link<TTo extends string = '.'>(
|
|
215
|
+
props: MakeLinkOptions<RegisteredAllRouteInfo, '/', TTo>,
|
|
216
|
+
): JSX.Element {
|
|
217
|
+
const router = useRouter()
|
|
218
|
+
return <router.Link {...(props as any)} />
|
|
219
|
+
}
|
|
159
220
|
|
|
160
|
-
|
|
161
|
-
const isDOM = Boolean(
|
|
162
|
-
typeof window !== 'undefined' &&
|
|
163
|
-
window.document &&
|
|
164
|
-
window.document.createElement,
|
|
165
|
-
)
|
|
221
|
+
type MatchesContextValue = RouteMatch[]
|
|
166
222
|
|
|
167
|
-
const
|
|
223
|
+
export const matchesContext = React.createContext<MatchesContextValue>(null!)
|
|
224
|
+
export const routerContext = React.createContext<{ router: RegisteredRouter }>(
|
|
225
|
+
null!,
|
|
226
|
+
)
|
|
168
227
|
|
|
169
228
|
export type MatchesProviderProps = {
|
|
170
|
-
value:
|
|
229
|
+
value: MatchesContextValue
|
|
171
230
|
children: React.ReactNode
|
|
172
231
|
}
|
|
173
232
|
|
|
@@ -175,7 +234,7 @@ export function MatchesProvider(props: MatchesProviderProps) {
|
|
|
175
234
|
return <matchesContext.Provider {...props} />
|
|
176
235
|
}
|
|
177
236
|
|
|
178
|
-
const useRouterSubscription = (router: Router<any, any>) => {
|
|
237
|
+
const useRouterSubscription = (router: Router<any, any, any>) => {
|
|
179
238
|
useSyncExternalStore(
|
|
180
239
|
(cb) => router.subscribe(() => cb()),
|
|
181
240
|
() => router.state,
|
|
@@ -185,10 +244,14 @@ const useRouterSubscription = (router: Router<any, any>) => {
|
|
|
185
244
|
|
|
186
245
|
export function createReactRouter<
|
|
187
246
|
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
188
|
-
|
|
247
|
+
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
248
|
+
TRouterContext = unknown,
|
|
249
|
+
>(
|
|
250
|
+
opts: RouterOptions<TRouteConfig, TRouterContext>,
|
|
251
|
+
): Router<TRouteConfig, TAllRouteInfo, TRouterContext> {
|
|
189
252
|
const makeRouteExt = (
|
|
190
253
|
route: AnyRoute,
|
|
191
|
-
router: Router<any, any>,
|
|
254
|
+
router: Router<any, any, any>,
|
|
192
255
|
): Pick<AnyRoute, 'useRoute' | 'linkProps' | 'Link' | 'MatchRoute'> => {
|
|
193
256
|
return {
|
|
194
257
|
useRoute: (subRouteId = '.' as any) => {
|
|
@@ -237,7 +300,7 @@ export function createReactRouter<
|
|
|
237
300
|
...rest
|
|
238
301
|
} = options
|
|
239
302
|
|
|
240
|
-
const linkInfo = route.buildLink(options)
|
|
303
|
+
const linkInfo = route.buildLink(options as any)
|
|
241
304
|
|
|
242
305
|
if (linkInfo.type === 'external') {
|
|
243
306
|
const { href } = linkInfo
|
|
@@ -253,6 +316,15 @@ export function createReactRouter<
|
|
|
253
316
|
next,
|
|
254
317
|
} = linkInfo
|
|
255
318
|
|
|
319
|
+
const reactHandleClick = (e: Event) => {
|
|
320
|
+
if (React.startTransition)
|
|
321
|
+
// This is a hack for react < 18
|
|
322
|
+
React.startTransition(() => {
|
|
323
|
+
handleClick(e)
|
|
324
|
+
})
|
|
325
|
+
else handleClick(e)
|
|
326
|
+
}
|
|
327
|
+
|
|
256
328
|
const composeHandlers =
|
|
257
329
|
(handlers: (undefined | ((e: any) => void))[]) =>
|
|
258
330
|
(e: React.SyntheticEvent) => {
|
|
@@ -275,7 +347,7 @@ export function createReactRouter<
|
|
|
275
347
|
...resolvedInactiveProps,
|
|
276
348
|
...rest,
|
|
277
349
|
href: disabled ? undefined : next.href,
|
|
278
|
-
onClick: composeHandlers([
|
|
350
|
+
onClick: composeHandlers([reactHandleClick, onClick]),
|
|
279
351
|
onFocus: composeHandlers([handleFocus, onFocus]),
|
|
280
352
|
onMouseEnter: composeHandlers([handleEnter, onMouseEnter]),
|
|
281
353
|
onMouseLeave: composeHandlers([handleLeave, onMouseLeave]),
|
|
@@ -344,12 +416,12 @@ export function createReactRouter<
|
|
|
344
416
|
const coreRouter = createRouter<TRouteConfig>({
|
|
345
417
|
...opts,
|
|
346
418
|
createRouter: (router) => {
|
|
347
|
-
const routerExt: Pick<Router<any, any>, 'useMatch' | 'useState'> = {
|
|
419
|
+
const routerExt: Pick<Router<any, any, any>, 'useMatch' | 'useState'> = {
|
|
348
420
|
useState: () => {
|
|
349
421
|
useRouterSubscription(router)
|
|
350
422
|
return router.state
|
|
351
423
|
},
|
|
352
|
-
useMatch: (routeId) => {
|
|
424
|
+
useMatch: (routeId, opts) => {
|
|
353
425
|
useRouterSubscription(router)
|
|
354
426
|
|
|
355
427
|
invariant(
|
|
@@ -357,36 +429,34 @@ export function createReactRouter<
|
|
|
357
429
|
`"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
|
|
358
430
|
)
|
|
359
431
|
|
|
360
|
-
const
|
|
432
|
+
const nearestMatch = useNearestMatch()
|
|
361
433
|
const match = router.state.matches.find((d) => d.routeId === routeId)
|
|
362
434
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
routeId as string
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!match) {
|
|
382
|
-
invariant('Match not found!')
|
|
435
|
+
if (opts?.strict ?? true) {
|
|
436
|
+
invariant(
|
|
437
|
+
match,
|
|
438
|
+
`Could not find an active match for "${routeId as string}"!`,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
invariant(
|
|
442
|
+
nearestMatch.routeId == match?.routeId,
|
|
443
|
+
`useMatch("${
|
|
444
|
+
match?.routeId as string
|
|
445
|
+
}") is being called in a component that is meant to render the '${
|
|
446
|
+
nearestMatch.routeId
|
|
447
|
+
}' route. Did you mean to 'useMatch("${
|
|
448
|
+
match?.routeId as string
|
|
449
|
+
}", { strict: false })' or 'useRoute("${
|
|
450
|
+
match?.routeId as string
|
|
451
|
+
}")' instead?`,
|
|
452
|
+
)
|
|
383
453
|
}
|
|
384
454
|
|
|
385
|
-
return match
|
|
455
|
+
return match as any
|
|
386
456
|
},
|
|
387
457
|
}
|
|
388
458
|
|
|
389
|
-
const routeExt = makeRouteExt(router.getRoute(
|
|
459
|
+
const routeExt = makeRouteExt(router.getRoute(rootRouteId), router)
|
|
390
460
|
|
|
391
461
|
Object.assign(router, routerExt, routeExt)
|
|
392
462
|
},
|
|
@@ -395,19 +465,13 @@ export function createReactRouter<
|
|
|
395
465
|
|
|
396
466
|
Object.assign(route, routeExt)
|
|
397
467
|
},
|
|
398
|
-
|
|
399
|
-
if (typeof
|
|
400
|
-
|
|
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
|
-
}
|
|
468
|
+
loadComponent: async (component) => {
|
|
469
|
+
if (component.preload && typeof document !== 'undefined') {
|
|
470
|
+
component.preload()
|
|
471
|
+
// return await component.preload()
|
|
408
472
|
}
|
|
409
473
|
|
|
410
|
-
return
|
|
474
|
+
return component as any
|
|
411
475
|
},
|
|
412
476
|
})
|
|
413
477
|
|
|
@@ -417,125 +481,184 @@ export function createReactRouter<
|
|
|
417
481
|
export type RouterProps<
|
|
418
482
|
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
419
483
|
TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
children?: React.ReactNode
|
|
484
|
+
TRouterContext = unknown,
|
|
485
|
+
> = RouterOptions<TRouteConfig, TRouterContext> & {
|
|
486
|
+
router: Router<TRouteConfig, TAllRouteInfo, TRouterContext>
|
|
424
487
|
}
|
|
425
488
|
|
|
426
489
|
export function RouterProvider<
|
|
427
490
|
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
428
491
|
TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
|
|
429
|
-
|
|
492
|
+
TRouterContext = unknown,
|
|
493
|
+
>({
|
|
494
|
+
router,
|
|
495
|
+
...rest
|
|
496
|
+
}: RouterProps<TRouteConfig, TAllRouteInfo, TRouterContext>) {
|
|
430
497
|
router.update(rest)
|
|
431
498
|
|
|
432
499
|
useRouterSubscription(router)
|
|
433
|
-
|
|
434
|
-
useLayoutEffect(() => {
|
|
500
|
+
React.useEffect(() => {
|
|
435
501
|
return router.mount()
|
|
436
502
|
}, [router])
|
|
437
503
|
|
|
438
504
|
return (
|
|
439
|
-
|
|
440
|
-
<
|
|
441
|
-
{
|
|
442
|
-
|
|
443
|
-
|
|
505
|
+
<>
|
|
506
|
+
<routerContext.Provider value={{ router: router as any }}>
|
|
507
|
+
<MatchesProvider value={[undefined!, ...router.state.matches]}>
|
|
508
|
+
<Outlet />
|
|
509
|
+
</MatchesProvider>
|
|
510
|
+
</routerContext.Provider>
|
|
511
|
+
</>
|
|
444
512
|
)
|
|
445
513
|
}
|
|
446
514
|
|
|
447
|
-
function useRouter():
|
|
515
|
+
export function useRouter(): RegisteredRouter {
|
|
448
516
|
const value = React.useContext(routerContext)
|
|
449
517
|
warning(!value, 'useRouter must be used inside a <Router> component!')
|
|
450
518
|
|
|
451
519
|
useRouterSubscription(value.router)
|
|
452
520
|
|
|
453
|
-
return value.router
|
|
521
|
+
return value.router
|
|
454
522
|
}
|
|
455
523
|
|
|
456
|
-
function useMatches(): RouteMatch[] {
|
|
524
|
+
export function useMatches(): RouteMatch[] {
|
|
457
525
|
return React.useContext(matchesContext)
|
|
458
526
|
}
|
|
459
527
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
528
|
+
export function useMatch<
|
|
529
|
+
TId extends keyof RegisteredAllRouteInfo['routeInfoById'],
|
|
530
|
+
TStrict extends boolean = true,
|
|
531
|
+
>(
|
|
532
|
+
routeId: TId,
|
|
533
|
+
opts?: { strict?: TStrict },
|
|
534
|
+
): TStrict extends true
|
|
535
|
+
? RouteMatch<
|
|
536
|
+
RegisteredAllRouteInfo,
|
|
537
|
+
RegisteredAllRouteInfo['routeInfoById'][TId]
|
|
538
|
+
>
|
|
539
|
+
:
|
|
540
|
+
| RouteMatch<
|
|
541
|
+
RegisteredAllRouteInfo,
|
|
542
|
+
RegisteredAllRouteInfo['routeInfoById'][TId]
|
|
543
|
+
>
|
|
544
|
+
| undefined {
|
|
475
545
|
const router = useRouter()
|
|
476
|
-
|
|
546
|
+
return router.useMatch(routeId as any, opts) as any
|
|
547
|
+
}
|
|
477
548
|
|
|
478
|
-
|
|
549
|
+
export function useNearestMatch(): RouteMatch<
|
|
550
|
+
RegisteredAllRouteInfo,
|
|
551
|
+
RouteInfo
|
|
552
|
+
> {
|
|
553
|
+
const runtimeMatch = useMatches()[0]
|
|
479
554
|
|
|
480
|
-
|
|
555
|
+
invariant(runtimeMatch, `Could not find a nearest match!`)
|
|
481
556
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return null
|
|
485
|
-
}
|
|
557
|
+
return runtimeMatch as any
|
|
558
|
+
}
|
|
486
559
|
|
|
487
|
-
|
|
488
|
-
|
|
560
|
+
export function useRoute<
|
|
561
|
+
TId extends keyof RegisteredAllRouteInfo['routeInfoById'],
|
|
562
|
+
>(
|
|
563
|
+
routeId: TId,
|
|
564
|
+
): Route<RegisteredAllRouteInfo, RegisteredAllRouteInfo['routeInfoById'][TId]> {
|
|
565
|
+
const router = useRouter()
|
|
566
|
+
return router.useRoute(routeId as any) as any
|
|
567
|
+
}
|
|
489
568
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
569
|
+
export function useSearch<
|
|
570
|
+
TId extends keyof RegisteredAllRouteInfo['routeInfoById'] = keyof RegisteredAllRouteInfo['routeInfoById'],
|
|
571
|
+
>(_routeId?: TId): RegisteredAllRouteInfo['fullSearchSchema'] {
|
|
572
|
+
return useRouter().state.location.search
|
|
573
|
+
}
|
|
494
574
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
575
|
+
export function linkProps<TTo extends string = '.'>(
|
|
576
|
+
props: MakeLinkPropsOptions<RegisteredAllRouteInfo, '/', TTo>,
|
|
577
|
+
): React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
578
|
+
const router = useRouter()
|
|
579
|
+
return router.linkProps(props as any)
|
|
580
|
+
}
|
|
501
581
|
|
|
502
|
-
|
|
503
|
-
|
|
582
|
+
export function MatchRoute<TTo extends string = '.'>(
|
|
583
|
+
props: MakeMatchRouteOptions<RegisteredAllRouteInfo, '/', TTo>,
|
|
584
|
+
): JSX.Element {
|
|
585
|
+
const router = useRouter()
|
|
586
|
+
return React.createElement(router.MatchRoute, props as any)
|
|
587
|
+
}
|
|
504
588
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
589
|
+
export function Outlet() {
|
|
590
|
+
const router = useRouter()
|
|
591
|
+
const matches = useMatches().slice(1)
|
|
592
|
+
const match = matches[0]
|
|
509
593
|
|
|
510
|
-
|
|
511
|
-
return (pendingElement as any) ?? null
|
|
512
|
-
}
|
|
513
|
-
}
|
|
594
|
+
const defaultPending = React.useCallback(() => null, [])
|
|
514
595
|
|
|
515
|
-
|
|
516
|
-
|
|
596
|
+
if (!match) {
|
|
597
|
+
return null
|
|
598
|
+
}
|
|
517
599
|
|
|
518
|
-
|
|
519
|
-
|
|
600
|
+
const PendingComponent = (match.__.pendingComponent ??
|
|
601
|
+
router.options.defaultPendingComponent ??
|
|
602
|
+
defaultPending) as any
|
|
520
603
|
|
|
521
|
-
const
|
|
522
|
-
|
|
604
|
+
const errorComponent =
|
|
605
|
+
match.__.errorComponent ?? router.options.defaultErrorComponent
|
|
523
606
|
|
|
524
607
|
return (
|
|
525
|
-
<MatchesProvider value={matches}
|
|
526
|
-
<
|
|
608
|
+
<MatchesProvider value={matches}>
|
|
609
|
+
<React.Suspense fallback={<PendingComponent />}>
|
|
610
|
+
<CatchBoundary
|
|
611
|
+
key={match.routeId}
|
|
612
|
+
errorComponent={errorComponent}
|
|
613
|
+
match={match as any}
|
|
614
|
+
>
|
|
615
|
+
{
|
|
616
|
+
((): React.ReactNode => {
|
|
617
|
+
if (match.status === 'error') {
|
|
618
|
+
throw match.error
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (match.status === 'success') {
|
|
622
|
+
return React.createElement(
|
|
623
|
+
(match.__.component as any) ??
|
|
624
|
+
router.options.defaultComponent ??
|
|
625
|
+
Outlet,
|
|
626
|
+
)
|
|
627
|
+
}
|
|
628
|
+
throw match.__.loadPromise
|
|
629
|
+
})() as JSX.Element
|
|
630
|
+
}
|
|
631
|
+
</CatchBoundary>
|
|
632
|
+
</React.Suspense>
|
|
633
|
+
{/* Provide a suffix suspense boundary to make sure the router is
|
|
634
|
+
ready to be dehydrated on the server */}
|
|
635
|
+
{/* {router.options.ssrFooter && match.matchId === rootRouteId ? (
|
|
636
|
+
<React.Suspense fallback={null}>
|
|
637
|
+
{(() => {
|
|
638
|
+
if (router.state.pending) {
|
|
639
|
+
throw router.navigationPromise
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return router.options.ssrFooter()
|
|
643
|
+
})()}
|
|
644
|
+
</React.Suspense>
|
|
645
|
+
) : null} */}
|
|
527
646
|
</MatchesProvider>
|
|
528
647
|
)
|
|
529
648
|
}
|
|
530
649
|
|
|
531
650
|
class CatchBoundary extends React.Component<{
|
|
532
651
|
children: any
|
|
533
|
-
|
|
652
|
+
errorComponent: any
|
|
653
|
+
match: RouteMatch
|
|
534
654
|
}> {
|
|
535
655
|
state = {
|
|
536
656
|
error: false,
|
|
657
|
+
info: undefined,
|
|
537
658
|
}
|
|
659
|
+
|
|
538
660
|
componentDidCatch(error: any, info: any) {
|
|
661
|
+
console.error(`Error in route match: ${this.props.match.matchId}`)
|
|
539
662
|
console.error(error)
|
|
540
663
|
|
|
541
664
|
this.setState({
|
|
@@ -543,23 +666,59 @@ class CatchBoundary extends React.Component<{
|
|
|
543
666
|
info,
|
|
544
667
|
})
|
|
545
668
|
}
|
|
546
|
-
|
|
547
|
-
this.setState({
|
|
548
|
-
error: false,
|
|
549
|
-
info: false,
|
|
550
|
-
})
|
|
551
|
-
}
|
|
669
|
+
|
|
552
670
|
render() {
|
|
553
|
-
|
|
671
|
+
return (
|
|
672
|
+
<CatchBoundaryInner
|
|
673
|
+
{...this.props}
|
|
674
|
+
errorState={this.state}
|
|
675
|
+
reset={() => this.setState({})}
|
|
676
|
+
/>
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// This is the messiest thing ever... I'm either seriously tired (likely) or
|
|
682
|
+
// there has to be a better way to reset error boundaries when the
|
|
683
|
+
// router's location key changes.
|
|
684
|
+
function CatchBoundaryInner(props: {
|
|
685
|
+
children: any
|
|
686
|
+
errorComponent: any
|
|
687
|
+
errorState: { error: unknown; info: any }
|
|
688
|
+
reset: () => void
|
|
689
|
+
}) {
|
|
690
|
+
const [activeErrorState, setActiveErrorState] = React.useState(
|
|
691
|
+
props.errorState,
|
|
692
|
+
)
|
|
693
|
+
const router = useRouter()
|
|
694
|
+
const errorComponent = props.errorComponent ?? DefaultErrorBoundary
|
|
695
|
+
|
|
696
|
+
React.useEffect(() => {
|
|
697
|
+
if (activeErrorState) {
|
|
698
|
+
let prevKey = router.state.location.key
|
|
699
|
+
return router.subscribe(() => {
|
|
700
|
+
if (router.state.location.key !== prevKey) {
|
|
701
|
+
prevKey = router.state.location.key
|
|
702
|
+
setActiveErrorState({} as any)
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return
|
|
708
|
+
}, [activeErrorState])
|
|
554
709
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
: catchElement
|
|
710
|
+
React.useEffect(() => {
|
|
711
|
+
if (props.errorState.error) {
|
|
712
|
+
setActiveErrorState(props.errorState)
|
|
559
713
|
}
|
|
714
|
+
props.reset()
|
|
715
|
+
}, [props.errorState.error])
|
|
560
716
|
|
|
561
|
-
|
|
717
|
+
if (activeErrorState.error) {
|
|
718
|
+
return React.createElement(errorComponent, activeErrorState)
|
|
562
719
|
}
|
|
720
|
+
|
|
721
|
+
return props.children
|
|
563
722
|
}
|
|
564
723
|
|
|
565
724
|
export function DefaultErrorBoundary({ error }: { error: any }) {
|
|
@@ -584,19 +743,6 @@ export function DefaultErrorBoundary({ error }: { error: any }) {
|
|
|
584
743
|
) : null}
|
|
585
744
|
</pre>
|
|
586
745
|
</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
746
|
</div>
|
|
601
747
|
)
|
|
602
748
|
}
|