@tanstack/react-router 0.0.1-beta.166 → 0.0.1-beta.168

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/src/index.tsx CHANGED
@@ -1,1088 +1,7 @@
1
- import * as React from 'react'
2
- import { NoInfer, useStore } from '@tanstack/react-store'
3
- import invariant from 'tiny-invariant'
4
- import warning from 'tiny-warning'
5
- import {
6
- functionalUpdate,
7
- last,
8
- pick,
9
- MatchRouteOptions,
10
- RegisteredRouter,
11
- RouterOptions,
12
- Router,
13
- RouteMatch,
14
- RouteByPath,
15
- AnyRoute,
16
- AnyRouteProps,
17
- LinkOptions,
18
- ToOptions,
19
- ResolveRelativePath,
20
- NavigateOptions,
21
- ResolveFullPath,
22
- ResolveId,
23
- AnySearchSchema,
24
- ParsePathParams,
25
- MergeParamsFromParent,
26
- RouteContext,
27
- AnyContext,
28
- UseLoaderResult,
29
- ResolveFullSearchSchema,
30
- Route,
31
- RouteConstraints,
32
- RoutePaths,
33
- RoutesById,
34
- RouteIds,
35
- RouteById,
36
- ParseRoute,
37
- AllParams,
38
- rootRouteId,
39
- } from '@tanstack/router-core'
40
-
41
1
  //
42
2
 
3
+ export { useStore } from '@tanstack/react-store'
43
4
  export * from '@tanstack/router-core'
5
+ export * from './react'
44
6
  export * from './scroll-restoration'
45
-
46
- export { useStore }
47
-
48
- declare module '@tanstack/router-core' {
49
- interface RegisterRouteComponent<TProps extends Record<string, any>> {
50
- RouteComponent: RouteComponent<TProps>
51
- }
52
-
53
- interface RegisterRouteErrorComponent<TProps extends Record<string, any>> {
54
- RouteErrorComponent: RouteComponent<TProps>
55
- }
56
-
57
- // Extend the Route class to have some React-Specific methods
58
- interface Route<
59
- TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute,
60
- TPath extends RouteConstraints['TPath'] = '/',
61
- TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
62
- TParentRoute,
63
- TPath
64
- >,
65
- TCustomId extends RouteConstraints['TCustomId'] = string,
66
- TId extends RouteConstraints['TId'] = ResolveId<
67
- TParentRoute,
68
- TCustomId,
69
- TPath
70
- >,
71
- TLoader = unknown,
72
- TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
73
- TFullSearchSchema extends RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchema<
74
- TParentRoute,
75
- TSearchSchema
76
- >,
77
- TParams extends RouteConstraints['TParams'] = Record<
78
- ParsePathParams<TPath>,
79
- string
80
- >,
81
- TAllParams extends RouteConstraints['TAllParams'] = MergeParamsFromParent<
82
- TParentRoute['types']['allParams'],
83
- TParams
84
- >,
85
- TParentContext extends RouteConstraints['TParentContext'] = TParentRoute['types']['routeContext'],
86
- TAllParentContext extends RouteConstraints['TAllParentContext'] = TParentRoute['types']['context'],
87
- TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
88
- TAllContext extends RouteConstraints['TAllContext'] = MergeParamsFromParent<
89
- TParentRoute['types']['context'],
90
- TRouteContext
91
- >,
92
- TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
93
- TChildren extends RouteConstraints['TChildren'] = unknown,
94
- TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
95
- > {
96
- useMatch: <TStrict extends boolean = true, TSelected = TAllContext>(opts?: {
97
- strict?: TStrict
98
- select?: (search: TAllContext) => TSelected
99
- }) => TStrict extends true ? TSelected : TSelected | undefined
100
- useLoader: <TStrict extends boolean = true, TSelected = TLoader>(opts?: {
101
- strict?: TStrict
102
- select?: (search: TLoader) => TSelected
103
- }) => TStrict extends true
104
- ? UseLoaderResult<TSelected>
105
- : UseLoaderResult<TSelected> | undefined
106
- useContext: <
107
- TStrict extends boolean = true,
108
- TSelected = TAllContext,
109
- >(opts?: {
110
- strict?: TStrict
111
- select?: (search: TAllContext) => TSelected
112
- }) => TStrict extends true ? TSelected : TSelected | undefined
113
- useRouteContext: <
114
- TStrict extends boolean = true,
115
- TSelected = TRouteContext,
116
- >(opts?: {
117
- strict?: TStrict
118
- select?: (search: TRouteContext) => TSelected
119
- }) => TStrict extends true ? TSelected : TSelected | undefined
120
- useSearch: <
121
- TStrict extends boolean = true,
122
- TSelected = TFullSearchSchema,
123
- >(opts?: {
124
- strict?: TStrict
125
- select?: (search: TFullSearchSchema) => TSelected
126
- }) => TStrict extends true ? TSelected : TSelected | undefined
127
- useParams: <TStrict extends boolean = true, TSelected = TAllParams>(opts?: {
128
- strict?: TStrict
129
- select?: (search: TAllParams) => TSelected
130
- }) => TStrict extends true ? TSelected : TSelected | undefined
131
- }
132
- }
133
-
134
- Route.__onInit = (route) => {
135
- Object.assign(route, {
136
- useMatch: (opts = {}) => {
137
- return useMatch({ ...opts, from: route.id }) as any
138
- },
139
- useLoader: (opts = {}) => {
140
- return useLoader({ ...opts, from: route.id }) as any
141
- },
142
- useContext: (opts: any = {}) => {
143
- return useMatch({
144
- ...opts,
145
- from: route.id,
146
- select: (d: any) => opts?.select?.(d.context) ?? d.context,
147
- } as any)
148
- },
149
- useRouteContext: (opts: any = {}) => {
150
- return useMatch({
151
- ...opts,
152
- from: route.id,
153
- select: (d: any) => opts?.select?.(d.routeContext) ?? d.routeContext,
154
- } as any)
155
- },
156
- useSearch: (opts = {}) => {
157
- return useSearch({ ...opts, from: route.id } as any)
158
- },
159
- useParams: (opts = {}) => {
160
- return useParams({ ...opts, from: route.id } as any)
161
- },
162
- })
163
- }
164
-
165
- //
166
-
167
- type ReactNode = any
168
-
169
- export type SyncRouteComponent<TProps> =
170
- | ((props: TProps) => ReactNode)
171
- | React.LazyExoticComponent<(props: TProps) => ReactNode>
172
-
173
- export type AsyncRouteComponent<TProps> = SyncRouteComponent<TProps> & {
174
- preload?: () => Promise<void>
175
- }
176
-
177
- export type RouteErrorComponent = AsyncRouteComponent<RouteErrorComponentProps>
178
-
179
- export type RouteErrorComponentProps = {
180
- error: Error
181
- info: { componentStack: string }
182
- }
183
-
184
- export type AnyRouteComponent = RouteComponent<AnyRouteProps>
185
-
186
- export type RouteComponent<TProps> = AsyncRouteComponent<TProps>
187
-
188
- export function lazyRouteComponent<
189
- T extends Record<string, any>,
190
- TKey extends keyof T = 'default',
191
- >(
192
- importer: () => Promise<T>,
193
- exportName?: TKey,
194
- ): T[TKey] extends (props: infer TProps) => any
195
- ? AsyncRouteComponent<TProps>
196
- : never {
197
- let loadPromise: Promise<any>
198
-
199
- const load = () => {
200
- if (!loadPromise) {
201
- loadPromise = importer()
202
- }
203
-
204
- return loadPromise
205
- }
206
-
207
- const lazyComp = React.lazy(async () => {
208
- const moduleExports = await load()
209
- const comp = moduleExports[exportName ?? 'default']
210
- return {
211
- default: comp,
212
- }
213
- })
214
-
215
- ;(lazyComp as any).preload = load
216
-
217
- return lazyComp as any
218
- }
219
-
220
- export type LinkPropsOptions<
221
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
222
- TTo extends string = '',
223
- > = LinkOptions<RegisteredRouter['routeTree'], TFrom, TTo> & {
224
- // 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)
225
- activeProps?:
226
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
227
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
228
- // 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)
229
- inactiveProps?:
230
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
231
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
232
- // If set to `true`, the link's underlying navigate() call will be wrapped in a `React.startTransition` call. Defaults to `true`.
233
- startTransition?: boolean
234
- }
235
-
236
- export type MakeUseMatchRouteOptions<
237
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
238
- TTo extends string = '',
239
- > = ToOptions<RegisteredRouter['routeTree'], TFrom, TTo> & MatchRouteOptions
240
-
241
- export type MakeMatchRouteOptions<
242
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
243
- TTo extends string = '',
244
- > = ToOptions<RegisteredRouter['routeTree'], TFrom, TTo> &
245
- MatchRouteOptions & {
246
- // 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
247
- children?:
248
- | ((
249
- params?: RouteByPath<
250
- RegisteredRouter['routeTree'],
251
- ResolveRelativePath<TFrom, NoInfer<TTo>>
252
- >['types']['allParams'],
253
- ) => ReactNode)
254
- | React.ReactNode
255
- }
256
-
257
- export type MakeLinkPropsOptions<
258
- TFrom extends string = '/',
259
- TTo extends string = '',
260
- > = LinkPropsOptions<TFrom, TTo> & React.AnchorHTMLAttributes<HTMLAnchorElement>
261
-
262
- export type MakeLinkOptions<
263
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
264
- TTo extends string = '',
265
- > = LinkPropsOptions<TFrom, TTo> &
266
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
267
- // 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
268
- children?:
269
- | React.ReactNode
270
- | ((state: { isActive: boolean }) => React.ReactNode)
271
- }
272
-
273
- export type PromptProps = {
274
- message: string
275
- condition?: boolean | any
276
- children?: ReactNode
277
- }
278
-
279
- //
280
-
281
- export function useLinkProps<
282
- TFrom extends string = '/',
283
- TTo extends string = '',
284
- >(
285
- options: MakeLinkPropsOptions<TFrom, TTo>,
286
- ): React.AnchorHTMLAttributes<HTMLAnchorElement> {
287
- const router = useRouter()
288
-
289
- const {
290
- // custom props
291
- type,
292
- children,
293
- target,
294
- activeProps = () => ({ className: 'active' }),
295
- inactiveProps = () => ({}),
296
- activeOptions,
297
- disabled,
298
- // fromCurrent,
299
- hash,
300
- search,
301
- params,
302
- to = '.',
303
- preload,
304
- preloadDelay,
305
- replace,
306
- // element props
307
- style,
308
- className,
309
- onClick,
310
- onFocus,
311
- onMouseEnter,
312
- onMouseLeave,
313
- onTouchStart,
314
- ...rest
315
- } = options
316
-
317
- const linkInfo = router.buildLink(options as any)
318
-
319
- if (linkInfo.type === 'external') {
320
- const { href } = linkInfo
321
- return { href }
322
- }
323
-
324
- const {
325
- handleClick,
326
- handleFocus,
327
- handleEnter,
328
- handleLeave,
329
- handleTouchStart,
330
- isActive,
331
- next,
332
- } = linkInfo
333
-
334
- const handleReactClick = (e: Event) => {
335
- if (options.startTransition ?? true) {
336
- ;(React.startTransition || ((d) => d))(() => {
337
- handleClick(e)
338
- })
339
- }
340
- }
341
-
342
- const composeHandlers =
343
- (handlers: (undefined | ((e: any) => void))[]) =>
344
- (e: React.SyntheticEvent) => {
345
- if (e.persist) e.persist()
346
- handlers.filter(Boolean).forEach((handler) => {
347
- if (e.defaultPrevented) return
348
- handler!(e)
349
- })
350
- }
351
-
352
- // Get the active props
353
- const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
354
- ? functionalUpdate(activeProps as any, {}) ?? {}
355
- : {}
356
-
357
- // Get the inactive props
358
- const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
359
- isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
360
-
361
- return {
362
- ...resolvedActiveProps,
363
- ...resolvedInactiveProps,
364
- ...rest,
365
- href: disabled ? undefined : next.href,
366
- onClick: composeHandlers([onClick, handleReactClick]),
367
- onFocus: composeHandlers([onFocus, handleFocus]),
368
- onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
369
- onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
370
- onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
371
- target,
372
- style: {
373
- ...style,
374
- ...resolvedActiveProps.style,
375
- ...resolvedInactiveProps.style,
376
- },
377
- className:
378
- [
379
- className,
380
- resolvedActiveProps.className,
381
- resolvedInactiveProps.className,
382
- ]
383
- .filter(Boolean)
384
- .join(' ') || undefined,
385
- ...(disabled
386
- ? {
387
- role: 'link',
388
- 'aria-disabled': true,
389
- }
390
- : undefined),
391
- ['data-status']: isActive ? 'active' : undefined,
392
- }
393
- }
394
-
395
- export interface LinkComponent<TProps extends Record<string, any> = {}> {
396
- <
397
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
398
- TTo extends string = '',
399
- >(
400
- props: MakeLinkOptions<TFrom, TTo> &
401
- TProps &
402
- React.RefAttributes<HTMLAnchorElement>,
403
- ): ReactNode
404
- }
405
-
406
- export const Link: LinkComponent = React.forwardRef((props: any, ref) => {
407
- const linkProps = useLinkProps(props)
408
-
409
- return (
410
- <a
411
- {...{
412
- ref: ref as any,
413
- ...linkProps,
414
- children:
415
- typeof props.children === 'function'
416
- ? props.children({
417
- isActive: (linkProps as any)['data-status'] === 'active',
418
- })
419
- : props.children,
420
- }}
421
- />
422
- )
423
- }) as any
424
-
425
- export function Navigate<
426
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
427
- TTo extends string = '',
428
- >(props: NavigateOptions<RegisteredRouter['routeTree'], TFrom, TTo>): null {
429
- const router = useRouter()
430
-
431
- React.useLayoutEffect(() => {
432
- router.navigate(props as any)
433
- }, [])
434
-
435
- return null
436
- }
437
-
438
- export const matchIdsContext = React.createContext<string[]>(null!)
439
- export const routerContext = React.createContext<RegisteredRouter>(null!)
440
-
441
- export type RouterProps<
442
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
443
- TDehydrated extends Record<string, any> = Record<string, any>,
444
- > = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> & {
445
- router: Router<TRouteTree>
446
- context?: Partial<RouterOptions<TRouteTree, TDehydrated>['context']>
447
- }
448
-
449
- export function useRouterState<TSelected = RegisteredRouter['state']>(opts?: {
450
- select: (state: RegisteredRouter['state']) => TSelected
451
- }): TSelected {
452
- const router = useRouter()
453
- return useStore(router.__store, opts?.select)
454
- }
455
-
456
- export function RouterProvider<
457
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
458
- TDehydrated extends Record<string, any> = Record<string, any>,
459
- >({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>) {
460
- router.update(rest)
461
-
462
- React.useEffect(() => {
463
- let unsub
464
-
465
- React.startTransition(() => {
466
- unsub = router.mount()
467
- })
468
-
469
- return unsub
470
- }, [router])
471
-
472
- const Wrap = router.options.Wrap || React.Fragment
473
-
474
- return (
475
- <React.Suspense fallback={null}>
476
- <Wrap>
477
- <routerContext.Provider value={router as any}>
478
- <Matches />
479
- </routerContext.Provider>
480
- </Wrap>
481
- </React.Suspense>
482
- )
483
- }
484
-
485
- function Matches() {
486
- const router = useRouter()
487
-
488
- const matchIds = useRouterState({
489
- select: (state) => {
490
- const hasPendingComponent = state.pendingMatches.some((d) => {
491
- const route = router.getRoute(d.routeId as any)
492
- return !!route?.options.pendingComponent
493
- })
494
-
495
- if (hasPendingComponent) {
496
- return state.pendingMatchIds
497
- }
498
-
499
- return state.matchIds
500
- },
501
- })
502
-
503
- return (
504
- <matchIdsContext.Provider value={[undefined!, ...matchIds]}>
505
- <CatchBoundary
506
- errorComponent={ErrorComponent}
507
- route={router.getRoute(rootRouteId)}
508
- onCatch={() => {
509
- warning(
510
- false,
511
- `Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`,
512
- )
513
- }}
514
- >
515
- <Outlet />
516
- </CatchBoundary>
517
- </matchIdsContext.Provider>
518
- )
519
- }
520
-
521
- export function useRouter(): RegisteredRouter {
522
- const value = React.useContext(routerContext)
523
- warning(value, 'useRouter must be used inside a <Router> component!')
524
- return value
525
- }
526
-
527
- export function useMatches<T = RouteMatch[]>(opts?: {
528
- select?: (matches: RouteMatch[]) => T
529
- }): T {
530
- const matchIds = React.useContext(matchIdsContext)
531
-
532
- return useRouterState({
533
- select: (state) => {
534
- const matches = state.matches.slice(
535
- state.matches.findIndex((d) => d.id === matchIds[0]),
536
- )
537
- return (opts?.select?.(matches) ?? matches) as T
538
- },
539
- })
540
- }
541
-
542
- export function useMatch<
543
- TFrom extends RouteIds<RegisteredRouter['routeTree']>,
544
- TStrict extends boolean = true,
545
- TRouteMatchState = RouteMatch<
546
- RegisteredRouter['routeTree'],
547
- RouteById<RegisteredRouter['routeTree'], TFrom>
548
- >,
549
- TSelected = TRouteMatchState,
550
- >(opts?: {
551
- from: TFrom
552
- strict?: TStrict
553
- select?: (match: TRouteMatchState) => TSelected
554
- }): TStrict extends true ? TRouteMatchState : TRouteMatchState | undefined {
555
- const router = useRouter()
556
- const nearestMatchId = React.useContext(matchIdsContext)[0]!
557
- const nearestMatchRouteId = router.getRouteMatch(nearestMatchId)?.routeId
558
-
559
- const matchRouteId = useRouterState({
560
- select: (state) => {
561
- const matches = state.matches
562
- const match = opts?.from
563
- ? matches.find((d) => d.routeId === opts?.from)
564
- : matches.find((d) => d.id === nearestMatchId)
565
-
566
- return match!.routeId
567
- },
568
- })
569
-
570
- if (opts?.strict ?? true) {
571
- invariant(
572
- nearestMatchRouteId == matchRouteId,
573
- `useMatch("${
574
- matchRouteId as string
575
- }") is being called in a component that is meant to render the '${nearestMatchRouteId}' route. Did you mean to 'useMatch("${
576
- matchRouteId as string
577
- }", { strict: false })' or 'useRoute("${
578
- matchRouteId as string
579
- }")' instead?`,
580
- )
581
- }
582
-
583
- const match = useRouterState({
584
- select: (state) => {
585
- const matches = state.matches
586
- const match = opts?.from
587
- ? matches.find((d) => d.routeId === opts?.from)
588
- : matches.find((d) => d.id === nearestMatchId)
589
-
590
- invariant(
591
- match,
592
- `Could not find ${
593
- opts?.from
594
- ? `an active match from "${opts.from}"`
595
- : 'a nearest match!'
596
- }`,
597
- )
598
-
599
- return (opts?.select?.(match as any) ?? match) as TSelected
600
- },
601
- })
602
-
603
- return match as any
604
- }
605
-
606
- export type RouteFromIdOrRoute<T> = T extends ParseRoute<
607
- RegisteredRouter['routeTree']
608
- >
609
- ? T
610
- : T extends RouteIds<RegisteredRouter['routeTree']>
611
- ? RoutesById<RegisteredRouter['routeTree']>[T]
612
- : T extends string
613
- ? RouteIds<RegisteredRouter['routeTree']>
614
- : never
615
-
616
- export function useLoader<
617
- TFrom extends RouteIds<RegisteredRouter['routeTree']>,
618
- TStrict extends boolean = true,
619
- TLoader = RouteById<RegisteredRouter['routeTree'], TFrom>['types']['loader'],
620
- TSelected = TLoader,
621
- >(opts?: {
622
- from: TFrom
623
- strict?: TStrict
624
- select?: (search: TLoader) => TSelected
625
- }): TStrict extends true ? TSelected : TSelected | undefined {
626
- return useMatch({
627
- ...(opts as any),
628
- select: (match: RouteMatch) =>
629
- (opts?.select?.(match.loaderData as TLoader) ??
630
- match.loaderData) as TSelected,
631
- })
632
- }
633
-
634
- export function useRouterContext<
635
- TFrom extends RouteIds<RegisteredRouter['routeTree']>,
636
- TStrict extends boolean = true,
637
- TContext = RouteById<
638
- RegisteredRouter['routeTree'],
639
- TFrom
640
- >['types']['context'],
641
- TSelected = TContext,
642
- >(opts?: {
643
- from: TFrom
644
- strict?: TStrict
645
- select?: (search: TContext) => TSelected
646
- }): TStrict extends true ? TSelected : TSelected | undefined {
647
- return useMatch({
648
- ...(opts as any),
649
- select: (match: RouteMatch) =>
650
- (opts?.select?.(match.context as TContext) ?? match.context) as TSelected,
651
- })
652
- }
653
-
654
- export function useRouteContext<
655
- TFrom extends RouteIds<RegisteredRouter['routeTree']>,
656
- TStrict extends boolean = true,
657
- TRouteContext = RouteById<
658
- RegisteredRouter['routeTree'],
659
- TFrom
660
- >['types']['routeContext'],
661
- TSelected = TRouteContext,
662
- >(opts?: {
663
- from: TFrom
664
- strict?: TStrict
665
- select?: (search: TRouteContext) => TSelected
666
- }): TStrict extends true ? TSelected : TSelected | undefined {
667
- return useMatch({
668
- ...(opts as any),
669
- select: (match: RouteMatch) =>
670
- (opts?.select?.(match.routeContext as TRouteContext) ??
671
- match.routeContext) as TSelected,
672
- })
673
- }
674
-
675
- export function useSearch<
676
- TFrom extends RouteIds<RegisteredRouter['routeTree']>,
677
- TStrict extends boolean = true,
678
- TSearch = RouteById<
679
- RegisteredRouter['routeTree'],
680
- TFrom
681
- >['types']['fullSearchSchema'],
682
- TSelected = TSearch,
683
- >(opts?: {
684
- from: TFrom
685
- strict?: TStrict
686
- select?: (search: TSearch) => TSelected
687
- }): TStrict extends true ? TSelected : TSelected | undefined {
688
- return useMatch({
689
- ...(opts as any),
690
- select: (match: RouteMatch) => {
691
- return (opts?.select?.(match.search as TSearch) ??
692
- match.search) as TSelected
693
- },
694
- })
695
- }
696
-
697
- export function useParams<
698
- TFrom extends RouteIds<RegisteredRouter['routeTree']> = '/',
699
- TDefaultSelected = AllParams<RegisteredRouter['routeTree']> &
700
- RouteById<RegisteredRouter['routeTree'], TFrom>['types']['allParams'],
701
- TSelected = TDefaultSelected,
702
- >(opts?: {
703
- from: TFrom
704
- select?: (search: TDefaultSelected) => TSelected
705
- }): TSelected {
706
- return useRouterState({
707
- select: (state: any) => {
708
- const params = (last(state.matches) as any)?.params
709
- return (opts?.select?.(params) ?? params) as TSelected
710
- },
711
- })
712
- }
713
-
714
- export function useNavigate<
715
- TDefaultFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
716
- >(defaultOpts?: { from?: TDefaultFrom }) {
717
- const router = useRouter()
718
- return React.useCallback(
719
- <
720
- TFrom extends RoutePaths<RegisteredRouter['routeTree']> = TDefaultFrom,
721
- TTo extends string = '',
722
- >(
723
- opts?: NavigateOptions<RegisteredRouter['routeTree'], TFrom, TTo>,
724
- ) => {
725
- return router.navigate({ ...defaultOpts, ...(opts as any) })
726
- },
727
- [],
728
- )
729
- }
730
-
731
- export function useMatchRoute() {
732
- const router = useRouter()
733
-
734
- return React.useCallback(
735
- <TFrom extends string = '/', TTo extends string = ''>(
736
- opts: MakeUseMatchRouteOptions<TFrom, TTo>,
737
- ) => {
738
- const { pending, caseSensitive, ...rest } = opts
739
-
740
- return router.matchRoute(rest as any, {
741
- pending,
742
- caseSensitive,
743
- })
744
- },
745
- [],
746
- )
747
- }
748
-
749
- export function MatchRoute<TFrom extends string = '/', TTo extends string = ''>(
750
- props: MakeMatchRouteOptions<TFrom, TTo>,
751
- ): any {
752
- const matchRoute = useMatchRoute()
753
- const params = matchRoute(props)
754
-
755
- if (typeof props.children === 'function') {
756
- return (props.children as any)(params)
757
- }
758
-
759
- return !!params ? props.children : null
760
- }
761
-
762
- export function Outlet() {
763
- const matchIds = React.useContext(matchIdsContext).slice(1)
764
-
765
- if (!matchIds[0]) {
766
- return null
767
- }
768
-
769
- return <Match matchIds={matchIds} />
770
- }
771
-
772
- const defaultPending = () => null
773
-
774
- function Match({ matchIds }: { matchIds: string[] }) {
775
- const router = useRouter()
776
- const matchId = matchIds[0]!
777
- const routeId = router.getRouteMatch(matchId)!.routeId
778
- const route = router.getRoute(routeId)
779
-
780
- const PendingComponent = (route.options.pendingComponent ??
781
- router.options.defaultPendingComponent ??
782
- defaultPending) as any
783
-
784
- const errorComponent =
785
- route.options.errorComponent ??
786
- router.options.defaultErrorComponent ??
787
- ErrorComponent
788
-
789
- const ResolvedSuspenseBoundary =
790
- route.options.wrapInSuspense ?? !route.isRoot
791
- ? React.Suspense
792
- : SafeFragment
793
-
794
- const ResolvedCatchBoundary = !!errorComponent ? CatchBoundary : SafeFragment
795
-
796
- return (
797
- <matchIdsContext.Provider value={matchIds}>
798
- <ResolvedSuspenseBoundary
799
- fallback={React.createElement(PendingComponent, {
800
- useMatch: route.useMatch,
801
- useContext: route.useContext,
802
- useRouteContext: route.useRouteContext,
803
- useSearch: route.useSearch,
804
- useParams: route.useParams,
805
- })}
806
- >
807
- <ResolvedCatchBoundary
808
- key={route.id}
809
- errorComponent={errorComponent}
810
- route={route}
811
- onCatch={() => {
812
- warning(false, `Error in route match: ${matchId}`)
813
- }}
814
- >
815
- <MatchInner matchId={matchId} PendingComponent={PendingComponent} />
816
- </ResolvedCatchBoundary>
817
- </ResolvedSuspenseBoundary>
818
- </matchIdsContext.Provider>
819
- )
820
- }
821
-
822
- function MatchInner({
823
- matchId,
824
- PendingComponent,
825
- }: {
826
- matchId: string
827
- PendingComponent: any
828
- }): any {
829
- const router = useRouter()
830
-
831
- const match = useRouterState({
832
- select: (d) => {
833
- const match = d.matchesById[matchId]
834
- return pick(match!, ['status', 'loadPromise', 'routeId', 'error'])
835
- },
836
- })
837
-
838
- const route = router.getRoute(match.routeId)
839
-
840
- if (match.status === 'error') {
841
- throw match.error
842
- }
843
-
844
- if (match.status === 'pending') {
845
- return React.createElement(PendingComponent, {
846
- useLoader: route.useLoader,
847
- useMatch: route.useMatch,
848
- useContext: route.useContext,
849
- useRouteContext: route.useRouteContext,
850
- useSearch: route.useSearch,
851
- useParams: route.useParams,
852
- })
853
- }
854
-
855
- if (match.status === 'success') {
856
- let comp = route.options.component ?? router.options.defaultComponent
857
-
858
- if (comp) {
859
- return React.createElement(comp, {
860
- useLoader: route.useLoader,
861
- useMatch: route.useMatch,
862
- useContext: route.useContext as any,
863
- useRouteContext: route.useRouteContext as any,
864
- useSearch: route.useSearch,
865
- useParams: route.useParams as any,
866
- })
867
- }
868
-
869
- return <Outlet />
870
- }
871
-
872
- invariant(
873
- false,
874
- 'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
875
- )
876
- }
877
-
878
- function SafeFragment(props: any) {
879
- return <>{props.children}</>
880
- }
881
-
882
- export function useInjectHtml() {
883
- const router = useRouter()
884
-
885
- return React.useCallback(
886
- (html: string | (() => Promise<string> | string)) => {
887
- router.injectHtml(html)
888
- },
889
- [],
890
- )
891
- }
892
-
893
- export function useDehydrate() {
894
- const router = useRouter()
895
-
896
- return React.useCallback(function dehydrate<T>(
897
- key: any,
898
- data: T | (() => Promise<T> | T),
899
- ) {
900
- return router.dehydrateData(key, data)
901
- },
902
- [])
903
- }
904
-
905
- export function useHydrate() {
906
- const router = useRouter()
907
-
908
- return function hydrate<T = unknown>(key: any) {
909
- return router.hydrateData(key) as T
910
- }
911
- }
912
-
913
- // This is the messiest thing ever... I'm either seriously tired (likely) or
914
- // there has to be a better way to reset error boundaries when the
915
- // router's location key changes.
916
-
917
- class CatchBoundary extends React.Component<{
918
- children: any
919
- errorComponent: any
920
- route: AnyRoute
921
- onCatch: (error: any, info: any) => void
922
- }> {
923
- state = {
924
- error: false,
925
- info: undefined,
926
- }
927
- componentDidCatch(error: any, info: any) {
928
- this.props.onCatch(error, info)
929
- this.setState({
930
- error,
931
- info,
932
- })
933
- }
934
- render() {
935
- return (
936
- <CatchBoundaryInner
937
- {...this.props}
938
- errorState={this.state}
939
- reset={() => this.setState({})}
940
- />
941
- )
942
- }
943
- }
944
-
945
- function CatchBoundaryInner(props: {
946
- children: any
947
- errorComponent: any
948
- route: AnyRoute
949
- errorState: { error: unknown; info: any }
950
- reset: () => void
951
- }) {
952
- const locationKey = useRouterState({
953
- select: (d) => d.resolvedLocation.key,
954
- })
955
-
956
- const [activeErrorState, setActiveErrorState] = React.useState(
957
- props.errorState,
958
- )
959
- const errorComponent = props.errorComponent ?? ErrorComponent
960
- const prevKeyRef = React.useRef('' as any)
961
-
962
- React.useEffect(() => {
963
- if (activeErrorState) {
964
- if (locationKey !== prevKeyRef.current) {
965
- setActiveErrorState({} as any)
966
- }
967
- }
968
-
969
- prevKeyRef.current = locationKey
970
- }, [activeErrorState, locationKey])
971
-
972
- React.useEffect(() => {
973
- if (props.errorState.error) {
974
- setActiveErrorState(props.errorState)
975
- }
976
- // props.reset()
977
- }, [props.errorState.error])
978
-
979
- if (props.errorState.error && activeErrorState.error) {
980
- return React.createElement(errorComponent, {
981
- ...activeErrorState,
982
- useMatch: props.route.useMatch,
983
- useContext: props.route.useContext,
984
- useRouteContext: props.route.useRouteContext,
985
- useSearch: props.route.useSearch,
986
- useParams: props.route.useParams,
987
- })
988
- }
989
-
990
- return props.children
991
- }
992
-
993
- export function ErrorComponent({ error }: { error: any }) {
994
- const [show, setShow] = React.useState(process.env.NODE_ENV !== 'production')
995
-
996
- return (
997
- <div style={{ padding: '.5rem', maxWidth: '100%' }}>
998
- <div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
999
- <strong style={{ fontSize: '1rem' }}>Something went wrong!</strong>
1000
- <button
1001
- style={{
1002
- appearance: 'none',
1003
- fontSize: '.6em',
1004
- border: '1px solid currentColor',
1005
- padding: '.1rem .2rem',
1006
- fontWeight: 'bold',
1007
- borderRadius: '.25rem',
1008
- }}
1009
- onClick={() => setShow((d) => !d)}
1010
- >
1011
- {show ? 'Hide Error' : 'Show Error'}
1012
- </button>
1013
- </div>
1014
- <div style={{ height: '.25rem' }} />
1015
- {show ? (
1016
- <div>
1017
- <pre
1018
- style={{
1019
- fontSize: '.7em',
1020
- border: '1px solid red',
1021
- borderRadius: '.25rem',
1022
- padding: '.3rem',
1023
- color: 'red',
1024
- overflow: 'auto',
1025
- }}
1026
- >
1027
- {error.message ? <code>{error.message}</code> : null}
1028
- </pre>
1029
- </div>
1030
- ) : null}
1031
- </div>
1032
- )
1033
- }
1034
-
1035
- export function useBlocker(
1036
- message: string,
1037
- condition: boolean | any = true,
1038
- ): void {
1039
- const router = useRouter()
1040
-
1041
- React.useEffect(() => {
1042
- if (!condition) return
1043
-
1044
- let unblock = router.history.block((retry, cancel) => {
1045
- if (window.confirm(message)) {
1046
- unblock()
1047
- retry()
1048
- }
1049
- })
1050
-
1051
- return unblock
1052
- })
1053
- }
1054
-
1055
- export function Block({ message, condition, children }: PromptProps) {
1056
- useBlocker(message, condition)
1057
- return (children ?? null) as ReactNode
1058
- }
1059
-
1060
- export function shallow<T>(objA: T, objB: T) {
1061
- if (Object.is(objA, objB)) {
1062
- return true
1063
- }
1064
-
1065
- if (
1066
- typeof objA !== 'object' ||
1067
- objA === null ||
1068
- typeof objB !== 'object' ||
1069
- objB === null
1070
- ) {
1071
- return false
1072
- }
1073
-
1074
- const keysA = Object.keys(objA)
1075
- if (keysA.length !== Object.keys(objB).length) {
1076
- return false
1077
- }
1078
-
1079
- for (let i = 0; i < keysA.length; i++) {
1080
- if (
1081
- !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
1082
- !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
1083
- ) {
1084
- return false
1085
- }
1086
- }
1087
- return true
1088
- }
7
+ export * from './awaited'