@tanstack/react-router 0.0.1-beta.18 → 0.0.1-beta.181

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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.18",
4
+ "version": "0.0.1-beta.181",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
- "homepage": "https://tanstack.com/router/",
7
+ "homepage": "https://tanstack.com/router",
8
8
  "description": "",
9
9
  "publishConfig": {
10
10
  "registry": "https://registry.npmjs.org/"
@@ -12,7 +12,6 @@
12
12
  "keywords": [
13
13
  "react",
14
14
  "location",
15
- "@tanstack/react-router",
16
15
  "router",
17
16
  "routing",
18
17
  "async",
@@ -24,7 +23,7 @@
24
23
  "url": "https://github.com/sponsors/tannerlinsley"
25
24
  },
26
25
  "module": "build/esm/index.js",
27
- "main": "build/cjs/react-router/src/index.js",
26
+ "main": "build/cjs/index.js",
28
27
  "browser": "build/umd/index.production.js",
29
28
  "types": "build/types/index.d.ts",
30
29
  "engines": {
@@ -41,12 +40,13 @@
41
40
  },
42
41
  "dependencies": {
43
42
  "@babel/runtime": "^7.16.7",
44
- "@tanstack/router-core": "0.0.1-beta.16",
45
- "react-lazy-with-preload": "^2.2.1",
46
- "use-sync-external-store": "^1.2.0"
43
+ "tiny-invariant": "^1.3.1",
44
+ "tiny-warning": "^1.0.3",
45
+ "@tanstack/react-store": "^0.0.1",
46
+ "@gisatcz/cross-package-react-context": "^0.2.0",
47
+ "@tanstack/router-core": "0.0.1-beta.181"
47
48
  },
48
- "devDependencies": {
49
- "@types/use-sync-external-store": "^0.0.3",
50
- "babel-plugin-transform-async-to-promises": "^0.8.18"
49
+ "scripts": {
50
+ "build": "rollup --config rollup.config.js"
51
51
  }
52
- }
52
+ }
@@ -0,0 +1,40 @@
1
+ import { DeferredPromise, isDehydratedDeferred } from '@tanstack/router-core'
2
+ import { useRouter } from './react'
3
+
4
+ export type AwaitOptions<T> = {
5
+ promise: DeferredPromise<T>
6
+ }
7
+
8
+ export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
9
+ const router = useRouter()
10
+
11
+ let state = promise.__deferredState
12
+ const key = `__TSR__DEFERRED__${state.uid}`
13
+
14
+ if (isDehydratedDeferred(promise)) {
15
+ state = router.hydrateData(key)!
16
+ promise = Promise.resolve(state.data) as DeferredPromise<any>
17
+ promise.__deferredState = state
18
+ }
19
+
20
+ if (state.status === 'pending') {
21
+ throw promise
22
+ }
23
+
24
+ if (state.status === 'error') {
25
+ throw state.error
26
+ }
27
+
28
+ router.dehydrateData(key, state)
29
+
30
+ return [state.data]
31
+ }
32
+
33
+ export function Await<T>(
34
+ props: AwaitOptions<T> & {
35
+ children: (result: T) => JSX.Element
36
+ },
37
+ ) {
38
+ const awaited = useAwaited(props)
39
+ return props.children(...awaited)
40
+ }
package/src/index.tsx CHANGED
@@ -1,598 +1,7 @@
1
- import * as React from 'react'
2
-
3
- import { useSyncExternalStore } from 'use-sync-external-store/shim'
4
-
5
- import {
6
- AnyRoute,
7
- CheckId,
8
- rootRouteId,
9
- Router,
10
- RouterState,
11
- ToIdOption,
12
- } from '@tanstack/router-core'
13
- import {
14
- warning,
15
- RouterOptions,
16
- RouteMatch,
17
- MatchRouteOptions,
18
- RouteConfig,
19
- AnyRouteConfig,
20
- AnyAllRouteInfo,
21
- DefaultAllRouteInfo,
22
- functionalUpdate,
23
- createRouter,
24
- AnyRouteInfo,
25
- AllRouteInfo,
26
- RouteInfo,
27
- ValidFromPath,
28
- LinkOptions,
29
- RouteInfoByPath,
30
- ResolveRelativePath,
31
- NoInfer,
32
- ToOptions,
33
- invariant,
34
- } from '@tanstack/router-core'
35
-
36
- export * from '@tanstack/router-core'
37
-
38
- export { lazyWithPreload as lazy } from 'react-lazy-with-preload/lib/index'
39
- export type { PreloadableComponent as LazyComponent } from 'react-lazy-with-preload'
40
-
41
- type SyncRouteComponent = (props?: {}) => React.ReactNode
42
- export type RouteComponent = SyncRouteComponent & {
43
- preload?: () => Promise<{
44
- default: SyncRouteComponent
45
- }>
46
- }
47
-
48
- declare module '@tanstack/router-core' {
49
- interface FrameworkGenerics {
50
- Component: RouteComponent
51
- }
52
-
53
- interface Router<
54
- TRouteConfig extends AnyRouteConfig = RouteConfig,
55
- TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
56
- > {
57
- useState: () => RouterState
58
- useRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
59
- routeId: TId,
60
- ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
61
- useMatch: <
62
- TId extends keyof TAllRouteInfo['routeInfoById'],
63
- TStrict extends true | false = true,
64
- >(
65
- routeId: TId,
66
- opts?: { strict?: TStrict },
67
- ) => TStrict extends true
68
- ? RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
69
- :
70
- | RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
71
- | undefined
72
- linkProps: <TTo extends string = '.'>(
73
- props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
74
- React.AnchorHTMLAttributes<HTMLAnchorElement>,
75
- ) => React.AnchorHTMLAttributes<HTMLAnchorElement>
76
- Link: <TTo extends string = '.'>(
77
- props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
78
- React.AnchorHTMLAttributes<HTMLAnchorElement> &
79
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
80
- // 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
81
- children?:
82
- | React.ReactNode
83
- | ((state: { isActive: boolean }) => React.ReactNode)
84
- },
85
- ) => JSX.Element
86
- MatchRoute: <TTo extends string = '.'>(
87
- props: ToOptions<TAllRouteInfo, '/', TTo> &
88
- MatchRouteOptions & {
89
- // 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
90
- children?:
91
- | React.ReactNode
92
- | ((
93
- params: RouteInfoByPath<
94
- TAllRouteInfo,
95
- ResolveRelativePath<'/', NoInfer<TTo>>
96
- >['allParams'],
97
- ) => React.ReactNode)
98
- },
99
- ) => JSX.Element
100
- }
101
-
102
- interface Route<
103
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
104
- TRouteInfo extends AnyRouteInfo = RouteInfo,
105
- > {
106
- useRoute: <
107
- TTo extends string = '.',
108
- TResolved extends string = ResolveRelativePath<
109
- TRouteInfo['id'],
110
- NoInfer<TTo>
111
- >,
112
- >(
113
- routeId: CheckId<
114
- TAllRouteInfo,
115
- TResolved,
116
- ToIdOption<TAllRouteInfo, TRouteInfo['id'], TTo>
117
- >,
118
- opts?: { strict?: boolean },
119
- ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TResolved]>
120
- linkProps: <TTo extends string = '.'>(
121
- props: LinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
122
- React.AnchorHTMLAttributes<HTMLAnchorElement>,
123
- ) => React.AnchorHTMLAttributes<HTMLAnchorElement>
124
- Link: <TTo extends string = '.'>(
125
- props: LinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
126
- React.AnchorHTMLAttributes<HTMLAnchorElement> &
127
- Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
128
- // 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
129
- children?:
130
- | React.ReactNode
131
- | ((state: { isActive: boolean }) => React.ReactNode)
132
- },
133
- ) => JSX.Element
134
- MatchRoute: <TTo extends string = '.'>(
135
- props: ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
136
- MatchRouteOptions & {
137
- // 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
138
- children?:
139
- | React.ReactNode
140
- | ((
141
- params: RouteInfoByPath<
142
- TAllRouteInfo,
143
- ResolveRelativePath<TRouteInfo['fullPath'], NoInfer<TTo>>
144
- >['allParams'],
145
- ) => React.ReactNode)
146
- },
147
- ) => JSX.Element
148
- }
149
- }
150
-
151
- type LinkPropsOptions<
152
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
153
- TFrom extends ValidFromPath<TAllRouteInfo> = '/',
154
- TTo extends string = '.',
155
- > = LinkOptions<TAllRouteInfo, TFrom, TTo> & {
156
- // 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)
157
- activeProps?:
158
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
159
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
160
- // 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)
161
- inactiveProps?:
162
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
163
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
164
- }
165
-
166
- export type PromptProps = {
167
- message: string
168
- when?: boolean | any
169
- children?: React.ReactNode
170
- }
171
-
172
1
  //
173
2
 
174
- const matchesContext = React.createContext<RouteMatch[]>(null!)
175
- const routerContext = React.createContext<{ router: Router<any, any> }>(null!)
176
-
177
- // Detect if we're in the DOM
178
- const isDOM = Boolean(
179
- typeof window !== 'undefined' &&
180
- window.document &&
181
- window.document.createElement,
182
- )
183
-
184
- const useLayoutEffect = isDOM ? React.useLayoutEffect : React.useEffect
185
-
186
- export type MatchesProviderProps = {
187
- value: RouteMatch[]
188
- children: React.ReactNode
189
- }
190
-
191
- export function MatchesProvider(props: MatchesProviderProps) {
192
- return <matchesContext.Provider {...props} />
193
- }
194
-
195
- const useRouterSubscription = (router: Router<any, any>) => {
196
- useSyncExternalStore(
197
- (cb) => router.subscribe(() => cb()),
198
- () => router.state,
199
- () => router.state,
200
- )
201
- }
202
-
203
- export function createReactRouter<
204
- TRouteConfig extends AnyRouteConfig = RouteConfig,
205
- >(opts: RouterOptions<TRouteConfig>): Router<TRouteConfig> {
206
- const makeRouteExt = (
207
- route: AnyRoute,
208
- router: Router<any, any>,
209
- ): Pick<AnyRoute, 'useRoute' | 'linkProps' | 'Link' | 'MatchRoute'> => {
210
- return {
211
- useRoute: (subRouteId = '.' as any) => {
212
- const resolvedRouteId = router.resolvePath(
213
- route.routeId,
214
- subRouteId as string,
215
- )
216
- const resolvedRoute = router.getRoute(resolvedRouteId)
217
- useRouterSubscription(router)
218
- invariant(
219
- resolvedRoute,
220
- `Could not find a route for route "${
221
- resolvedRouteId as string
222
- }"! Did you forget to add it to your route config?`,
223
- )
224
- return resolvedRoute
225
- },
226
- linkProps: (options) => {
227
- const {
228
- // custom props
229
- type,
230
- children,
231
- target,
232
- activeProps = () => ({ className: 'active' }),
233
- inactiveProps = () => ({}),
234
- activeOptions,
235
- disabled,
236
- // fromCurrent,
237
- hash,
238
- search,
239
- params,
240
- to,
241
- preload,
242
- preloadDelay,
243
- preloadMaxAge,
244
- replace,
245
- // element props
246
- style,
247
- className,
248
- onClick,
249
- onFocus,
250
- onMouseEnter,
251
- onMouseLeave,
252
- onTouchStart,
253
- onTouchEnd,
254
- ...rest
255
- } = options
256
-
257
- const linkInfo = route.buildLink(options)
258
-
259
- if (linkInfo.type === 'external') {
260
- const { href } = linkInfo
261
- return { href }
262
- }
263
-
264
- const {
265
- handleClick,
266
- handleFocus,
267
- handleEnter,
268
- handleLeave,
269
- isActive,
270
- next,
271
- } = linkInfo
272
-
273
- const reactHandleClick = (e: Event) => {
274
- React.startTransition(() => {
275
- handleClick(e)
276
- })
277
- }
278
-
279
- const composeHandlers =
280
- (handlers: (undefined | ((e: any) => void))[]) =>
281
- (e: React.SyntheticEvent) => {
282
- e.persist()
283
- handlers.forEach((handler) => {
284
- if (handler) handler(e)
285
- })
286
- }
287
-
288
- // Get the active props
289
- const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
290
- isActive ? functionalUpdate(activeProps, {}) ?? {} : {}
291
-
292
- // Get the inactive props
293
- const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
294
- isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
295
-
296
- return {
297
- ...resolvedActiveProps,
298
- ...resolvedInactiveProps,
299
- ...rest,
300
- href: disabled ? undefined : next.href,
301
- onClick: composeHandlers([reactHandleClick, onClick]),
302
- onFocus: composeHandlers([handleFocus, onFocus]),
303
- onMouseEnter: composeHandlers([handleEnter, onMouseEnter]),
304
- onMouseLeave: composeHandlers([handleLeave, onMouseLeave]),
305
- target,
306
- style: {
307
- ...style,
308
- ...resolvedActiveProps.style,
309
- ...resolvedInactiveProps.style,
310
- },
311
- className:
312
- [
313
- className,
314
- resolvedActiveProps.className,
315
- resolvedInactiveProps.className,
316
- ]
317
- .filter(Boolean)
318
- .join(' ') || undefined,
319
- ...(disabled
320
- ? {
321
- role: 'link',
322
- 'aria-disabled': true,
323
- }
324
- : undefined),
325
- ['data-status']: isActive ? 'active' : undefined,
326
- }
327
- },
328
- Link: React.forwardRef((props: any, ref) => {
329
- const linkProps = route.linkProps(props)
330
-
331
- useRouterSubscription(router)
332
-
333
- return (
334
- <a
335
- {...{
336
- ref: ref as any,
337
- ...linkProps,
338
- children:
339
- typeof props.children === 'function'
340
- ? props.children({
341
- isActive: (linkProps as any)['data-status'] === 'active',
342
- })
343
- : props.children,
344
- }}
345
- />
346
- )
347
- }) as any,
348
- MatchRoute: (opts) => {
349
- const { pending, caseSensitive, children, ...rest } = opts
350
-
351
- const params = route.matchRoute(rest as any, {
352
- pending,
353
- caseSensitive,
354
- })
355
-
356
- if (!params) {
357
- return null
358
- }
359
-
360
- return typeof opts.children === 'function'
361
- ? opts.children(params as any)
362
- : (opts.children as any)
363
- },
364
- }
365
- }
366
-
367
- const coreRouter = createRouter<TRouteConfig>({
368
- ...opts,
369
- createRouter: (router) => {
370
- const routerExt: Pick<Router<any, any>, 'useMatch' | 'useState'> = {
371
- useState: () => {
372
- useRouterSubscription(router)
373
- return router.state
374
- },
375
- useMatch: (routeId, opts) => {
376
- useRouterSubscription(router)
377
-
378
- invariant(
379
- routeId !== rootRouteId,
380
- `"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
381
- )
382
-
383
- const runtimeMatch = useMatches()?.[0]!
384
- const match = router.state.matches.find((d) => d.routeId === routeId)
385
-
386
- if (opts?.strict ?? true) {
387
- invariant(
388
- match,
389
- `Could not find an active match for "${routeId as string}"!`,
390
- )
391
-
392
- invariant(
393
- runtimeMatch.routeId == match?.routeId,
394
- `useMatch("${
395
- match?.routeId as string
396
- }") is being called in a component that is meant to render the '${
397
- runtimeMatch.routeId
398
- }' route. Did you mean to 'useMatch("${
399
- match?.routeId as string
400
- }", { strict: false })' or 'useRoute("${
401
- match?.routeId as string
402
- }")' instead?`,
403
- )
404
- }
405
-
406
- return match as any
407
- },
408
- }
409
-
410
- const routeExt = makeRouteExt(router.getRoute('/'), router)
411
-
412
- Object.assign(router, routerExt, routeExt)
413
- },
414
- createRoute: ({ router, route }) => {
415
- const routeExt = makeRouteExt(route, router)
416
-
417
- Object.assign(route, routeExt)
418
- },
419
- loadComponent: async (component) => {
420
- if (component.preload && typeof document !== 'undefined') {
421
- component.preload()
422
- // return await component.preload()
423
- }
424
-
425
- return component as any
426
- },
427
- })
428
-
429
- return coreRouter as any
430
- }
431
-
432
- export type RouterProps<
433
- TRouteConfig extends AnyRouteConfig = RouteConfig,
434
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
435
- > = RouterOptions<TRouteConfig> & {
436
- router: Router<TRouteConfig, TAllRouteInfo>
437
- // Children will default to `<Outlet />` if not provided
438
- children?: React.ReactNode
439
- }
440
-
441
- export function RouterProvider<
442
- TRouteConfig extends AnyRouteConfig = RouteConfig,
443
- TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
444
- >({ children, router, ...rest }: RouterProps<TRouteConfig, TAllRouteInfo>) {
445
- router.update(rest)
446
-
447
- useRouterSubscription(router)
448
- React.useEffect(() => {
449
- return router.mount()
450
- }, [router])
451
-
452
- return (
453
- <routerContext.Provider value={{ router }}>
454
- <MatchesProvider value={router.state.matches}>
455
- {children ?? <Outlet />}
456
- </MatchesProvider>
457
- </routerContext.Provider>
458
- )
459
- }
460
-
461
- export function useRouter(): Router {
462
- const value = React.useContext(routerContext)
463
- warning(!value, 'useRouter must be used inside a <Router> component!')
464
-
465
- useRouterSubscription(value.router)
466
-
467
- return value.router as Router
468
- }
469
-
470
- export function useMatches(): RouteMatch[] {
471
- return React.useContext(matchesContext)
472
- }
473
-
474
- export function Outlet() {
475
- const router = useRouter()
476
- const matches = useMatches().slice(1)
477
- const match = matches[0]
478
-
479
- const defaultPending = React.useCallback(() => null, [])
480
-
481
- if (!match) {
482
- return null
483
- }
484
-
485
- const PendingComponent = (match.__.pendingComponent ??
486
- router.options.defaultPendingComponent ??
487
- defaultPending) as any
488
-
489
- const errorComponent =
490
- match.__.errorComponent ?? router.options.defaultErrorComponent
491
-
492
- return (
493
- <MatchesProvider value={matches}>
494
- <React.Suspense fallback={<PendingComponent />}>
495
- <CatchBoundary errorComponent={errorComponent}>
496
- {
497
- ((): React.ReactNode => {
498
- if (match.status === 'error') {
499
- throw match.error
500
- }
501
-
502
- if (match.status === 'success') {
503
- return React.createElement(
504
- (match.__.component as any) ??
505
- router.options.defaultComponent ??
506
- Outlet,
507
- )
508
- }
509
-
510
- if (match.__.loadPromise) {
511
- console.log(match.matchId, 'suspend')
512
- throw match.__.loadPromise
513
- }
514
-
515
- invariant(false, 'This should never happen!')
516
- })() as JSX.Element
517
- }
518
- </CatchBoundary>
519
- </React.Suspense>
520
- </MatchesProvider>
521
- )
522
- }
523
-
524
- class CatchBoundary extends React.Component<{
525
- children: any
526
- errorComponent: any
527
- }> {
528
- state = {
529
- error: false,
530
- }
531
- componentDidCatch(error: any, info: any) {
532
- console.error(error)
533
-
534
- this.setState({
535
- error,
536
- info,
537
- })
538
- }
539
- render() {
540
- const errorComponent = this.props.errorComponent ?? DefaultErrorBoundary
541
-
542
- if (this.state.error) {
543
- return React.createElement(errorComponent, this.state)
544
- }
545
-
546
- return this.props.children
547
- }
548
- }
549
-
550
- export function DefaultErrorBoundary({ error }: { error: any }) {
551
- return (
552
- <div style={{ padding: '.5rem', maxWidth: '100%' }}>
553
- <strong style={{ fontSize: '1.2rem' }}>Something went wrong!</strong>
554
- <div style={{ height: '.5rem' }} />
555
- <div>
556
- <pre>
557
- {error.message ? (
558
- <code
559
- style={{
560
- fontSize: '.7em',
561
- border: '1px solid red',
562
- borderRadius: '.25rem',
563
- padding: '.5rem',
564
- color: 'red',
565
- }}
566
- >
567
- {error.message}
568
- </code>
569
- ) : null}
570
- </pre>
571
- </div>
572
- </div>
573
- )
574
- }
575
-
576
- export function usePrompt(message: string, when: boolean | any): void {
577
- const router = useRouter()
578
-
579
- React.useEffect(() => {
580
- if (!when) return
581
-
582
- let unblock = router.history.block((transition) => {
583
- if (window.confirm(message)) {
584
- unblock()
585
- transition.retry()
586
- } else {
587
- router.location.pathname = window.location.pathname
588
- }
589
- })
590
-
591
- return unblock
592
- }, [when, location, message])
593
- }
594
-
595
- export function Prompt({ message, when, children }: PromptProps) {
596
- usePrompt(message, when ?? true)
597
- return (children ?? null) as React.ReactNode
598
- }
3
+ export { useStore } from '@tanstack/react-store'
4
+ export * from '@tanstack/router-core'
5
+ export * from './react'
6
+ export * from './scroll-restoration'
7
+ export * from './awaited'