@tanstack/react-router 0.0.1-beta.19 → 0.0.1-beta.190

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