@tanstack/react-router 0.0.1-alpha.9 → 0.0.1-beta.10

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-alpha.9",
4
+ "version": "0.0.1-beta.10",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
- "homepage": "https://react-router.tanstack.com/",
7
+ "homepage": "https://tanstack.com/router/",
8
8
  "description": "",
9
9
  "publishConfig": {
10
10
  "registry": "https://registry.npmjs.org/"
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@babel/runtime": "^7.16.7",
43
- "@tanstack/router-core": "0.0.1-alpha.9",
43
+ "@tanstack/router-core": "0.0.1-beta.10",
44
44
  "use-sync-external-store": "^1.2.0"
45
45
  },
46
46
  "devDependencies": {
package/src/index.tsx CHANGED
@@ -4,10 +4,11 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'
4
4
 
5
5
  import {
6
6
  AnyRoute,
7
- RootRouteId,
7
+ CheckId,
8
8
  rootRouteId,
9
9
  Router,
10
10
  RouterState,
11
+ ToIdOption,
11
12
  } from '@tanstack/router-core'
12
13
  import {
13
14
  warning,
@@ -37,26 +38,29 @@ export * from '@tanstack/router-core'
37
38
  declare module '@tanstack/router-core' {
38
39
  interface FrameworkGenerics {
39
40
  Element: React.ReactNode
40
- AsyncElement: (opts: {
41
- params: Record<string, string>
42
- }) => Promise<React.ReactNode>
43
- SyncOrAsyncElement: React.ReactNode | FrameworkGenerics['AsyncElement']
41
+ // Any is required here so import() will work without having to do import().then(d => d.default)
42
+ SyncOrAsyncElement: React.ReactNode | (() => Promise<any>)
44
43
  }
45
44
 
46
45
  interface Router<
47
46
  TRouteConfig extends AnyRouteConfig = RouteConfig,
48
47
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
49
- > extends Pick<
50
- Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][RootRouteId]>,
51
- 'linkProps' | 'Link' | 'MatchRoute'
52
- > {
48
+ > {
53
49
  useState: () => RouterState
54
50
  useRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
55
51
  routeId: TId,
56
52
  ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
57
- useMatch: <TId extends keyof TAllRouteInfo['routeInfoById']>(
53
+ useMatch: <
54
+ TId extends keyof TAllRouteInfo['routeInfoById'],
55
+ TStrict extends true | false = true,
56
+ >(
58
57
  routeId: TId,
59
- ) => RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
58
+ opts?: { strict?: TStrict },
59
+ ) => TStrict extends true
60
+ ? RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
61
+ :
62
+ | RouteMatch<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
63
+ | undefined
60
64
  linkProps: <TTo extends string = '.'>(
61
65
  props: LinkPropsOptions<TAllRouteInfo, '/', TTo> &
62
66
  React.AnchorHTMLAttributes<HTMLAnchorElement>,
@@ -91,6 +95,20 @@ declare module '@tanstack/router-core' {
91
95
  TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
92
96
  TRouteInfo extends AnyRouteInfo = RouteInfo,
93
97
  > {
98
+ useRoute: <
99
+ TTo extends string = '.',
100
+ TResolved extends string = ResolveRelativePath<
101
+ TRouteInfo['id'],
102
+ NoInfer<TTo>
103
+ >,
104
+ >(
105
+ routeId: CheckId<
106
+ TAllRouteInfo,
107
+ TResolved,
108
+ ToIdOption<TAllRouteInfo, TRouteInfo['id'], TTo>
109
+ >,
110
+ opts?: { strict?: boolean },
111
+ ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TResolved]>
94
112
  linkProps: <TTo extends string = '.'>(
95
113
  props: LinkPropsOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo> &
96
114
  React.AnchorHTMLAttributes<HTMLAnchorElement>,
@@ -170,6 +188,7 @@ const useRouterSubscription = (router: Router<any, any>) => {
170
188
  useSyncExternalStore(
171
189
  (cb) => router.subscribe(() => cb()),
172
190
  () => router.state,
191
+ () => router.state,
173
192
  )
174
193
  }
175
194
 
@@ -179,8 +198,23 @@ export function createReactRouter<
179
198
  const makeRouteExt = (
180
199
  route: AnyRoute,
181
200
  router: Router<any, any>,
182
- ): Pick<AnyRoute, 'linkProps' | 'Link' | 'MatchRoute'> => {
201
+ ): Pick<AnyRoute, 'useRoute' | 'linkProps' | 'Link' | 'MatchRoute'> => {
183
202
  return {
203
+ useRoute: (subRouteId = '.' as any) => {
204
+ const resolvedRouteId = router.resolvePath(
205
+ route.routeId,
206
+ subRouteId as string,
207
+ )
208
+ const resolvedRoute = router.getRoute(resolvedRouteId)
209
+ useRouterSubscription(router)
210
+ invariant(
211
+ resolvedRoute,
212
+ `Could not find a route for route "${
213
+ resolvedRouteId as string
214
+ }"! Did you forget to add it to your route config?`,
215
+ )
216
+ return resolvedRoute
217
+ },
184
218
  linkProps: (options) => {
185
219
  const {
186
220
  // custom props
@@ -319,26 +353,12 @@ export function createReactRouter<
319
353
  const coreRouter = createRouter<TRouteConfig>({
320
354
  ...opts,
321
355
  createRouter: (router) => {
322
- const routerExt: Pick<
323
- Router<any, any>,
324
- 'useRoute' | 'useMatch' | 'useState'
325
- > = {
356
+ const routerExt: Pick<Router<any, any>, 'useMatch' | 'useState'> = {
326
357
  useState: () => {
327
358
  useRouterSubscription(router)
328
359
  return router.state
329
360
  },
330
- useRoute: (routeId) => {
331
- const route = router.getRoute(routeId)
332
- useRouterSubscription(router)
333
- invariant(
334
- route,
335
- `Could not find a route for route "${
336
- routeId as string
337
- }"! Did you forget to add it to your route config?`,
338
- )
339
- return route
340
- },
341
- useMatch: (routeId) => {
361
+ useMatch: (routeId, opts) => {
342
362
  useRouterSubscription(router)
343
363
 
344
364
  invariant(
@@ -346,32 +366,30 @@ export function createReactRouter<
346
366
  `"${rootRouteId}" cannot be used with useMatch! Did you mean to useRoute("${rootRouteId}")?`,
347
367
  )
348
368
 
349
- const runtimeMatch = useMatch()
369
+ const runtimeMatch = useMatches()?.[0]!
350
370
  const match = router.state.matches.find((d) => d.routeId === routeId)
351
371
 
352
- invariant(
353
- match,
354
- `Could not find a match for route "${
355
- routeId as string
356
- }" being rendered in this component!`,
357
- )
358
-
359
- invariant(
360
- runtimeMatch.routeId == match?.routeId,
361
- `useMatch('${
362
- match?.routeId as string
363
- }') is being called in a component that is meant to render the '${
364
- runtimeMatch.routeId
365
- }' route. Did you mean to 'useRoute(${
366
- match?.routeId as string
367
- })' instead?`,
368
- )
369
-
370
- if (!match) {
371
- invariant('Match not found!')
372
+ if (opts?.strict ?? true) {
373
+ invariant(
374
+ match,
375
+ `Could not find an active match for "${routeId as string}"!`,
376
+ )
377
+
378
+ invariant(
379
+ runtimeMatch.routeId == match?.routeId,
380
+ `useMatch('${
381
+ match?.routeId as string
382
+ }') is being called in a component that is meant to render the '${
383
+ runtimeMatch.routeId
384
+ }' route. Did you mean to 'useMatch(${
385
+ match?.routeId as string
386
+ }, { strict: false })' or 'useRoute(${
387
+ match?.routeId as string
388
+ })' instead?`,
389
+ )
372
390
  }
373
391
 
374
- return match
392
+ return match as any
375
393
  },
376
394
  }
377
395
 
@@ -384,6 +402,20 @@ export function createReactRouter<
384
402
 
385
403
  Object.assign(route, routeExt)
386
404
  },
405
+ createElement: async (element) => {
406
+ if (typeof element === 'function') {
407
+ const res = (await element()) as any
408
+
409
+ // Support direct import() calls
410
+ if (typeof res === 'object' && res.default) {
411
+ return React.createElement(res.default)
412
+ } else {
413
+ return res
414
+ }
415
+ }
416
+
417
+ return element
418
+ },
387
419
  })
388
420
 
389
421
  return coreRouter as any
@@ -405,9 +437,10 @@ export function RouterProvider<
405
437
  router.update(rest)
406
438
 
407
439
  useRouterSubscription(router)
408
-
409
440
  useLayoutEffect(() => {
410
- return router.mount()
441
+ const unsub = router.mount()
442
+ router.load()
443
+ return unsub
411
444
  }, [router])
412
445
 
413
446
  return (
@@ -432,57 +465,41 @@ function useMatches(): RouteMatch[] {
432
465
  return React.useContext(matchesContext)
433
466
  }
434
467
 
435
- // function useParentMatches(): RouteMatch[] {
436
- // const router = useRouter()
437
- // const match = useMatch()
438
- // const matches = router.state.matches
439
- // return matches.slice(
440
- // 0,
441
- // matches.findIndex((d) => d.matchId === match.matchId) - 1,
442
- // )
443
- // }
444
-
445
- function useMatch<T>(): RouteMatch {
446
- return useMatches()?.[0] as RouteMatch
447
- }
448
-
449
468
  export function Outlet() {
450
469
  const router = useRouter()
451
- const [, ...matches] = useMatches()
452
-
453
- const childMatch = matches[0]
470
+ const matches = useMatches().slice(1)
471
+ const match = matches[0]
454
472
 
455
- if (!childMatch) return null
473
+ if (!match) {
474
+ return null
475
+ }
456
476
 
457
477
  const element = ((): React.ReactNode => {
458
- if (!childMatch) {
478
+ if (!match) {
459
479
  return null
460
480
  }
461
481
 
462
482
  const errorElement =
463
- childMatch.__.errorElement ?? router.options.defaultErrorElement
483
+ match.__.errorElement ?? router.options.defaultErrorElement
464
484
 
465
- if (childMatch.status === 'error') {
485
+ if (match.status === 'error') {
466
486
  if (errorElement) {
467
487
  return errorElement as any
468
488
  }
469
489
 
470
- if (
471
- childMatch.options.useErrorBoundary ||
472
- router.options.useErrorBoundary
473
- ) {
474
- throw childMatch.error
490
+ if (match.options.useErrorBoundary || router.options.useErrorBoundary) {
491
+ throw match.error
475
492
  }
476
493
 
477
- return <DefaultErrorBoundary error={childMatch.error} />
494
+ return <DefaultErrorBoundary error={match.error} />
478
495
  }
479
496
 
480
- if (childMatch.status === 'loading' || childMatch.status === 'idle') {
481
- if (childMatch.isPending) {
497
+ if (match.status === 'loading' || match.status === 'idle') {
498
+ if (match.isPending) {
482
499
  const pendingElement =
483
- childMatch.__.pendingElement ?? router.options.defaultPendingElement
500
+ match.__.pendingElement ?? router.options.defaultPendingElement
484
501
 
485
- if (childMatch.options.pendingMs || pendingElement) {
502
+ if (match.options.pendingMs || pendingElement) {
486
503
  return (pendingElement as any) ?? null
487
504
  }
488
505
  }
@@ -490,14 +507,16 @@ export function Outlet() {
490
507
  return null
491
508
  }
492
509
 
493
- return (childMatch.__.element as any) ?? router.options.defaultElement
510
+ return (
511
+ (match.__.element as any) ?? router.options.defaultElement ?? <Outlet />
512
+ )
494
513
  })() as JSX.Element
495
514
 
496
515
  const catchElement =
497
- childMatch?.options.catchElement ?? router.options.defaultCatchElement
516
+ match?.options.catchElement ?? router.options.defaultCatchElement
498
517
 
499
518
  return (
500
- <MatchesProvider value={matches} key={childMatch.matchId}>
519
+ <MatchesProvider value={matches}>
501
520
  <CatchBoundary catchElement={catchElement}>{element}</CatchBoundary>
502
521
  </MatchesProvider>
503
522
  )
@@ -518,12 +537,6 @@ class CatchBoundary extends React.Component<{
518
537
  info,
519
538
  })
520
539
  }
521
- reset = () => {
522
- this.setState({
523
- error: false,
524
- info: false,
525
- })
526
- }
527
540
  render() {
528
541
  const catchElement = this.props.catchElement ?? DefaultErrorBoundary
529
542
 
@@ -559,7 +572,7 @@ export function DefaultErrorBoundary({ error }: { error: any }) {
559
572
  ) : null}
560
573
  </pre>
561
574
  </div>
562
- <div style={{ height: '1rem' }} />
575
+ {/* <div style={{ height: '1rem' }} />
563
576
  <div
564
577
  style={{
565
578
  fontSize: '.8em',
@@ -571,9 +584,31 @@ export function DefaultErrorBoundary({ error }: { error: any }) {
571
584
  If you are the owner of this website, it's highly recommended that you
572
585
  configure your own custom Catch/Error boundaries for the router. You can
573
586
  optionally configure a boundary for each route.
574
- </div>
587
+ </div> */}
575
588
  </div>
576
589
  )
577
590
  }
578
591
 
579
- // TODO: Add prompt
592
+ export function usePrompt(message: string, when: boolean | any): void {
593
+ const router = useRouter()
594
+
595
+ React.useEffect(() => {
596
+ if (!when) return
597
+
598
+ let unblock = router.history.block((transition) => {
599
+ if (window.confirm(message)) {
600
+ unblock()
601
+ transition.retry()
602
+ } else {
603
+ router.location.pathname = window.location.pathname
604
+ }
605
+ })
606
+
607
+ return unblock
608
+ }, [when, location, message])
609
+ }
610
+
611
+ export function Prompt({ message, when, children }: PromptProps) {
612
+ usePrompt(message, when ?? true)
613
+ return (children ?? null) as React.ReactNode
614
+ }
package/src/qss.ts DELETED
@@ -1,53 +0,0 @@
1
- // @ts-nocheck
2
-
3
- // We're inlining qss here for compression's sake, but we've included it as a hard dependency for the MIT license it requires.
4
-
5
- export function encode(obj, pfx?: string) {
6
- var k,
7
- i,
8
- tmp,
9
- str = ''
10
-
11
- for (k in obj) {
12
- if ((tmp = obj[k]) !== void 0) {
13
- if (Array.isArray(tmp)) {
14
- for (i = 0; i < tmp.length; i++) {
15
- str && (str += '&')
16
- str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i])
17
- }
18
- } else {
19
- str && (str += '&')
20
- str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp)
21
- }
22
- }
23
- }
24
-
25
- return (pfx || '') + str
26
- }
27
-
28
- function toValue(mix) {
29
- if (!mix) return ''
30
- var str = decodeURIComponent(mix)
31
- if (str === 'false') return false
32
- if (str === 'true') return true
33
- return +str * 0 === 0 ? +str : str
34
- }
35
-
36
- export function decode(str) {
37
- var tmp,
38
- k,
39
- out = {},
40
- arr = str.split('&')
41
-
42
- while ((tmp = arr.shift())) {
43
- tmp = tmp.split('=')
44
- k = tmp.shift()
45
- if (out[k] !== void 0) {
46
- out[k] = [].concat(out[k], toValue(tmp.shift()))
47
- } else {
48
- out[k] = toValue(tmp.shift())
49
- }
50
- }
51
-
52
- return out
53
- }