@tanstack/react-router 0.0.1-beta.167 → 0.0.1-beta.169

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