@depup/tanstack__react-router 1.167.5-depup.0 → 1.168.2-depup.0

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.
Files changed (113) hide show
  1. package/README.md +3 -4
  2. package/changes.json +2 -6
  3. package/dist/cjs/Match.cjs +147 -58
  4. package/dist/cjs/Match.cjs.map +1 -1
  5. package/dist/cjs/Matches.cjs +22 -24
  6. package/dist/cjs/Matches.cjs.map +1 -1
  7. package/dist/cjs/Scripts.cjs +36 -32
  8. package/dist/cjs/Scripts.cjs.map +1 -1
  9. package/dist/cjs/Transitioner.cjs +10 -16
  10. package/dist/cjs/Transitioner.cjs.map +1 -1
  11. package/dist/cjs/fileRoute.cjs +4 -6
  12. package/dist/cjs/fileRoute.cjs.map +1 -1
  13. package/dist/cjs/headContentUtils.cjs +147 -59
  14. package/dist/cjs/headContentUtils.cjs.map +1 -1
  15. package/dist/cjs/index.cjs +1 -1
  16. package/dist/cjs/index.dev.cjs +1 -1
  17. package/dist/cjs/link.cjs +34 -29
  18. package/dist/cjs/link.cjs.map +1 -1
  19. package/dist/cjs/not-found.cjs +20 -2
  20. package/dist/cjs/not-found.cjs.map +1 -1
  21. package/dist/cjs/renderRouteNotFound.cjs +3 -3
  22. package/dist/cjs/renderRouteNotFound.cjs.map +1 -1
  23. package/dist/cjs/route.cjs +0 -2
  24. package/dist/cjs/route.cjs.map +1 -1
  25. package/dist/cjs/router.cjs +2 -1
  26. package/dist/cjs/router.cjs.map +1 -1
  27. package/dist/cjs/routerStores.cjs +21 -0
  28. package/dist/cjs/routerStores.cjs.map +1 -0
  29. package/dist/cjs/routerStores.d.cts +7 -0
  30. package/dist/cjs/ssr/RouterClient.cjs +1 -1
  31. package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
  32. package/dist/cjs/ssr/renderRouterToStream.cjs +2 -2
  33. package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
  34. package/dist/cjs/ssr/renderRouterToString.cjs +1 -1
  35. package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
  36. package/dist/cjs/useCanGoBack.cjs +7 -2
  37. package/dist/cjs/useCanGoBack.cjs.map +1 -1
  38. package/dist/cjs/useLocation.cjs +21 -2
  39. package/dist/cjs/useLocation.cjs.map +1 -1
  40. package/dist/cjs/useMatch.cjs +35 -11
  41. package/dist/cjs/useMatch.cjs.map +1 -1
  42. package/dist/cjs/useRouter.cjs +3 -3
  43. package/dist/cjs/useRouter.cjs.map +1 -1
  44. package/dist/cjs/useRouterState.cjs +2 -2
  45. package/dist/cjs/useRouterState.cjs.map +1 -1
  46. package/dist/esm/Match.js +148 -57
  47. package/dist/esm/Match.js.map +1 -1
  48. package/dist/esm/Matches.js +23 -24
  49. package/dist/esm/Matches.js.map +1 -1
  50. package/dist/esm/Scripts.js +36 -32
  51. package/dist/esm/Scripts.js.map +1 -1
  52. package/dist/esm/Transitioner.js +10 -16
  53. package/dist/esm/Transitioner.js.map +1 -1
  54. package/dist/esm/fileRoute.js +4 -4
  55. package/dist/esm/fileRoute.js.map +1 -1
  56. package/dist/esm/headContentUtils.js +148 -60
  57. package/dist/esm/headContentUtils.js.map +1 -1
  58. package/dist/esm/index.dev.js +1 -1
  59. package/dist/esm/index.js +1 -1
  60. package/dist/esm/link.js +34 -29
  61. package/dist/esm/link.js.map +1 -1
  62. package/dist/esm/not-found.js +20 -2
  63. package/dist/esm/not-found.js.map +1 -1
  64. package/dist/esm/renderRouteNotFound.js +3 -2
  65. package/dist/esm/renderRouteNotFound.js.map +1 -1
  66. package/dist/esm/route.js +0 -2
  67. package/dist/esm/route.js.map +1 -1
  68. package/dist/esm/router.js +2 -1
  69. package/dist/esm/router.js.map +1 -1
  70. package/dist/esm/routerStores.d.ts +7 -0
  71. package/dist/esm/routerStores.js +20 -0
  72. package/dist/esm/routerStores.js.map +1 -0
  73. package/dist/esm/ssr/RouterClient.js +1 -1
  74. package/dist/esm/ssr/RouterClient.js.map +1 -1
  75. package/dist/esm/ssr/renderRouterToStream.js +2 -2
  76. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  77. package/dist/esm/ssr/renderRouterToString.js +1 -1
  78. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  79. package/dist/esm/useCanGoBack.js +6 -2
  80. package/dist/esm/useCanGoBack.js.map +1 -1
  81. package/dist/esm/useLocation.js +20 -2
  82. package/dist/esm/useLocation.js.map +1 -1
  83. package/dist/esm/useMatch.js +35 -10
  84. package/dist/esm/useMatch.js.map +1 -1
  85. package/dist/esm/useRouter.js +3 -2
  86. package/dist/esm/useRouter.js.map +1 -1
  87. package/dist/esm/useRouterState.js +2 -2
  88. package/dist/esm/useRouterState.js.map +1 -1
  89. package/dist/llms/rules/api.d.ts +1 -1
  90. package/dist/llms/rules/api.js +13 -19
  91. package/dist/llms/rules/guide.d.ts +1 -1
  92. package/dist/llms/rules/guide.js +27 -6
  93. package/package.json +5 -11
  94. package/src/Match.tsx +274 -81
  95. package/src/Matches.tsx +48 -30
  96. package/src/Scripts.tsx +72 -44
  97. package/src/Transitioner.tsx +24 -16
  98. package/src/fileRoute.ts +7 -9
  99. package/src/headContentUtils.tsx +210 -27
  100. package/src/link.tsx +66 -71
  101. package/src/not-found.tsx +41 -4
  102. package/src/renderRouteNotFound.tsx +6 -6
  103. package/src/route.tsx +0 -2
  104. package/src/router.ts +2 -1
  105. package/src/routerStores.ts +26 -0
  106. package/src/ssr/RouterClient.tsx +1 -1
  107. package/src/ssr/renderRouterToStream.tsx +2 -2
  108. package/src/ssr/renderRouterToString.tsx +1 -1
  109. package/src/useCanGoBack.ts +14 -2
  110. package/src/useLocation.tsx +32 -5
  111. package/src/useMatch.tsx +68 -19
  112. package/src/useRouter.tsx +7 -5
  113. package/src/useRouterState.tsx +4 -2
package/src/link.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react'
2
+ import { useStore } from '@tanstack/react-store'
2
3
  import { flushSync } from 'react-dom'
3
4
  import {
4
5
  deepEqual,
@@ -9,7 +10,6 @@ import {
9
10
  removeTrailingSlash,
10
11
  } from '@tanstack/router-core'
11
12
  import { isServer } from '@tanstack/router-core/isServer'
12
- import { useRouterState } from './useRouterState'
13
13
  import { useRouter } from './useRouter'
14
14
 
15
15
  import { useForwardedRef, useIntersectionObserver } from './utils'
@@ -102,7 +102,7 @@ export function useLinkProps<
102
102
  //
103
103
  // For SSR parity (to avoid hydration errors), we still compute the link's
104
104
  // active status on the server, but we avoid creating any router-state
105
- // subscriptions by reading from `router.state` directly.
105
+ // subscriptions by reading from the location store directly.
106
106
  //
107
107
  // Note: `location.hash` is not available on the server.
108
108
  // ==========================================================================
@@ -204,7 +204,7 @@ export function useLinkProps<
204
204
  const isActive = (() => {
205
205
  if (externalLink) return false
206
206
 
207
- const currentLocation = router.state.location
207
+ const currentLocation = router.stores.location.state
208
208
 
209
209
  const exact = activeOptions?.exact ?? false
210
210
 
@@ -377,32 +377,13 @@ export function useLinkProps<
377
377
  // eslint-disable-next-line react-hooks/rules-of-hooks
378
378
  const isHydrated = useHydrated()
379
379
 
380
- // subscribe to path/search/hash/params to re-build location when they change
381
- // eslint-disable-next-line react-hooks/rules-of-hooks
382
- const currentLocationState = useRouterState({
383
- select: (s) => {
384
- const leaf = s.matches[s.matches.length - 1]
385
- return {
386
- search: leaf?.search,
387
- hash: s.location.hash,
388
- path: leaf?.pathname, // path + params
389
- }
390
- },
391
- structuralSharing: true as any,
392
- })
393
-
394
- const from = options.from
395
-
396
380
  // eslint-disable-next-line react-hooks/rules-of-hooks
397
381
  const _options = React.useMemo(
398
- () => {
399
- return { ...options, from }
400
- },
382
+ () => options,
401
383
  // eslint-disable-next-line react-hooks/exhaustive-deps
402
384
  [
403
385
  router,
404
- currentLocationState,
405
- from,
386
+ options.from,
406
387
  options._fromLocation,
407
388
  options.hash,
408
389
  options.to,
@@ -415,11 +396,18 @@ export function useLinkProps<
415
396
  )
416
397
 
417
398
  // eslint-disable-next-line react-hooks/rules-of-hooks
418
- const next = React.useMemo(
419
- () => router.buildLocation({ ..._options } as any),
420
- [router, _options],
399
+ const currentLocation = useStore(
400
+ router.stores.location,
401
+ (l) => l,
402
+ (prev, next) => prev.href === next.href,
421
403
  )
422
404
 
405
+ // eslint-disable-next-line react-hooks/rules-of-hooks
406
+ const next = React.useMemo(() => {
407
+ const opts = { _fromLocation: currentLocation, ..._options }
408
+ return router.buildLocation(opts as any)
409
+ }, [router, currentLocation, _options])
410
+
423
411
  // Use publicHref - it contains the correct href for display
424
412
  // When a rewrite changes the origin, publicHref is the full URL
425
413
  // Otherwise it's the origin-stripped path
@@ -474,54 +462,61 @@ export function useLinkProps<
474
462
  }, [to, hrefOption, router.protocolAllowlist])
475
463
 
476
464
  // eslint-disable-next-line react-hooks/rules-of-hooks
477
- const isActive = useRouterState({
478
- select: (s) => {
479
- if (externalLink) return false
480
- if (activeOptions?.exact) {
481
- const testExact = exactPathTest(
482
- s.location.pathname,
483
- next.pathname,
484
- router.basepath,
485
- )
486
- if (!testExact) {
487
- return false
488
- }
489
- } else {
490
- const currentPathSplit = removeTrailingSlash(
491
- s.location.pathname,
492
- router.basepath,
493
- )
494
- const nextPathSplit = removeTrailingSlash(
495
- next.pathname,
496
- router.basepath,
497
- )
498
-
499
- const pathIsFuzzyEqual =
500
- currentPathSplit.startsWith(nextPathSplit) &&
501
- (currentPathSplit.length === nextPathSplit.length ||
502
- currentPathSplit[nextPathSplit.length] === '/')
503
-
504
- if (!pathIsFuzzyEqual) {
505
- return false
506
- }
465
+ const isActive = React.useMemo(() => {
466
+ if (externalLink) return false
467
+ if (activeOptions?.exact) {
468
+ const testExact = exactPathTest(
469
+ currentLocation.pathname,
470
+ next.pathname,
471
+ router.basepath,
472
+ )
473
+ if (!testExact) {
474
+ return false
507
475
  }
508
-
509
- if (activeOptions?.includeSearch ?? true) {
510
- const searchTest = deepEqual(s.location.search, next.search, {
511
- partial: !activeOptions?.exact,
512
- ignoreUndefined: !activeOptions?.explicitUndefined,
513
- })
514
- if (!searchTest) {
515
- return false
516
- }
476
+ } else {
477
+ const currentPathSplit = removeTrailingSlash(
478
+ currentLocation.pathname,
479
+ router.basepath,
480
+ )
481
+ const nextPathSplit = removeTrailingSlash(next.pathname, router.basepath)
482
+
483
+ const pathIsFuzzyEqual =
484
+ currentPathSplit.startsWith(nextPathSplit) &&
485
+ (currentPathSplit.length === nextPathSplit.length ||
486
+ currentPathSplit[nextPathSplit.length] === '/')
487
+
488
+ if (!pathIsFuzzyEqual) {
489
+ return false
517
490
  }
491
+ }
518
492
 
519
- if (activeOptions?.includeHash) {
520
- return isHydrated && s.location.hash === next.hash
493
+ if (activeOptions?.includeSearch ?? true) {
494
+ const searchTest = deepEqual(currentLocation.search, next.search, {
495
+ partial: !activeOptions?.exact,
496
+ ignoreUndefined: !activeOptions?.explicitUndefined,
497
+ })
498
+ if (!searchTest) {
499
+ return false
521
500
  }
522
- return true
523
- },
524
- })
501
+ }
502
+
503
+ if (activeOptions?.includeHash) {
504
+ return isHydrated && currentLocation.hash === next.hash
505
+ }
506
+ return true
507
+ }, [
508
+ activeOptions?.exact,
509
+ activeOptions?.explicitUndefined,
510
+ activeOptions?.includeHash,
511
+ activeOptions?.includeSearch,
512
+ currentLocation,
513
+ externalLink,
514
+ isHydrated,
515
+ next.hash,
516
+ next.pathname,
517
+ next.search,
518
+ router.basepath,
519
+ ])
525
520
 
526
521
  // Get the active props
527
522
  const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
package/src/not-found.tsx CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as React from 'react'
2
2
  import { isNotFound } from '@tanstack/router-core'
3
+ import { isServer } from '@tanstack/router-core/isServer'
4
+ import { useStore } from '@tanstack/react-store'
3
5
  import { CatchBoundary } from './CatchBoundary'
4
- import { useRouterState } from './useRouterState'
6
+ import { useRouter } from './useRouter'
5
7
  import type { ErrorInfo } from 'react'
6
8
  import type { NotFoundError } from '@tanstack/router-core'
7
9
 
@@ -10,10 +12,45 @@ export function CatchNotFound(props: {
10
12
  onCatch?: (error: Error, errorInfo: ErrorInfo) => void
11
13
  children: React.ReactNode
12
14
  }) {
15
+ const router = useRouter()
16
+
17
+ if (isServer ?? router.isServer) {
18
+ const pathname = router.stores.location.state.pathname
19
+ const status = router.stores.status.state
20
+ const resetKey = `not-found-${pathname}-${status}`
21
+
22
+ return (
23
+ <CatchBoundary
24
+ getResetKey={() => resetKey}
25
+ onCatch={(error, errorInfo) => {
26
+ if (isNotFound(error)) {
27
+ props.onCatch?.(error, errorInfo)
28
+ } else {
29
+ throw error
30
+ }
31
+ }}
32
+ errorComponent={({ error }) => {
33
+ if (isNotFound(error)) {
34
+ return props.fallback?.(error)
35
+ } else {
36
+ throw error
37
+ }
38
+ }}
39
+ >
40
+ {props.children}
41
+ </CatchBoundary>
42
+ )
43
+ }
44
+
13
45
  // TODO: Some way for the user to programmatically reset the not-found boundary?
14
- const resetKey = useRouterState({
15
- select: (s) => `not-found-${s.location.pathname}-${s.status}`,
16
- })
46
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
47
+ const pathname = useStore(
48
+ router.stores.location,
49
+ (location) => location.pathname,
50
+ )
51
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
52
+ const status = useStore(router.stores.status, (status) => status)
53
+ const resetKey = `not-found-${pathname}-${status}`
17
54
 
18
55
  return (
19
56
  <CatchBoundary
@@ -1,5 +1,4 @@
1
1
  import * as React from 'react'
2
- import warning from 'tiny-warning'
3
2
  import { DefaultGlobalNotFound } from './not-found'
4
3
  import type { AnyRoute, AnyRouter } from '@tanstack/router-core'
5
4
 
@@ -21,11 +20,12 @@ export function renderRouteNotFound(
21
20
  return <router.options.defaultNotFoundComponent {...data} />
22
21
  }
23
22
 
24
- if (process.env.NODE_ENV === 'development') {
25
- warning(
26
- route.options.notFoundComponent,
27
- `A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<p>Not Found</p>)`,
28
- )
23
+ if (process.env.NODE_ENV !== 'production') {
24
+ if (!route.options.notFoundComponent) {
25
+ console.warn(
26
+ `Warning: A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<p>Not Found</p>)`,
27
+ )
28
+ }
29
29
  }
30
30
 
31
31
  return <DefaultGlobalNotFound />
package/src/route.tsx CHANGED
@@ -258,7 +258,6 @@ export class Route<
258
258
  >,
259
259
  ) {
260
260
  super(options)
261
- ;(this as any).$$typeof = Symbol.for('react.memo')
262
261
  }
263
262
 
264
263
  useMatch: UseMatchRoute<TId> = (opts) => {
@@ -530,7 +529,6 @@ export class RootRoute<
530
529
  >,
531
530
  ) {
532
531
  super(options)
533
- ;(this as any).$$typeof = Symbol.for('react.memo')
534
532
  }
535
533
 
536
534
  useMatch: UseMatchRoute<RootRouteId> = (opts) => {
package/src/router.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RouterCore } from '@tanstack/router-core'
2
2
  import { createFileRoute, createLazyFileRoute } from './fileRoute'
3
+ import { getStoreFactory } from './routerStores'
3
4
  import type { RouterHistory } from '@tanstack/history'
4
5
  import type {
5
6
  AnyRoute,
@@ -114,7 +115,7 @@ export class Router<
114
115
  TDehydrated
115
116
  >,
116
117
  ) {
117
- super(options)
118
+ super(options, getStoreFactory)
118
119
  }
119
120
  }
120
121
 
@@ -0,0 +1,26 @@
1
+ import { batch, createStore } from '@tanstack/react-store'
2
+ import {
3
+ createNonReactiveMutableStore,
4
+ createNonReactiveReadonlyStore,
5
+ } from '@tanstack/router-core'
6
+ import { isServer } from '@tanstack/router-core/isServer'
7
+ import type { Readable } from '@tanstack/react-store'
8
+ import type { GetStoreConfig } from '@tanstack/router-core'
9
+
10
+ declare module '@tanstack/router-core' {
11
+ export interface RouterReadableStore<TValue> extends Readable<TValue> {}
12
+ }
13
+ export const getStoreFactory: GetStoreConfig = (opts) => {
14
+ if (isServer ?? opts.isServer) {
15
+ return {
16
+ createMutableStore: createNonReactiveMutableStore,
17
+ createReadonlyStore: createNonReactiveReadonlyStore,
18
+ batch: (fn) => fn(),
19
+ }
20
+ }
21
+ return {
22
+ createMutableStore: createStore,
23
+ createReadonlyStore: createStore,
24
+ batch: batch,
25
+ }
26
+ }
@@ -7,7 +7,7 @@ let hydrationPromise: Promise<void | Array<Array<void>>> | undefined
7
7
 
8
8
  export function RouterClient(props: { router: AnyRouter }) {
9
9
  if (!hydrationPromise) {
10
- if (!props.router.state.matches.length) {
10
+ if (!props.router.stores.matchesId.state.length) {
11
11
  hydrationPromise = hydrate(props.router)
12
12
  } else {
13
13
  hydrationPromise = Promise.resolve()
@@ -36,7 +36,7 @@ export const renderRouterToStream = async ({
36
36
  stream as unknown as ReadableStream,
37
37
  )
38
38
  return new Response(responseStream as any, {
39
- status: router.state.statusCode,
39
+ status: router.stores.statusCode.state,
40
40
  headers: responseHeaders,
41
41
  })
42
42
  }
@@ -79,7 +79,7 @@ export const renderRouterToStream = async ({
79
79
  reactAppPassthrough,
80
80
  )
81
81
  return new Response(responseStream as any, {
82
- status: router.state.statusCode,
82
+ status: router.stores.statusCode.state,
83
83
  headers: responseHeaders,
84
84
  })
85
85
  }
@@ -21,7 +21,7 @@ export const renderRouterToString = async ({
21
21
  }
22
22
 
23
23
  return new Response(`<!DOCTYPE html>${html}`, {
24
- status: router.state.statusCode,
24
+ status: router.stores.statusCode.state,
25
25
  headers: responseHeaders,
26
26
  })
27
27
  } catch (error) {
@@ -1,5 +1,17 @@
1
- import { useRouterState } from './useRouterState'
1
+ import { useStore } from '@tanstack/react-store'
2
+ import { isServer } from '@tanstack/router-core/isServer'
3
+ import { useRouter } from './useRouter'
2
4
 
3
5
  export function useCanGoBack() {
4
- return useRouterState({ select: (s) => s.location.state.__TSR_index !== 0 })
6
+ const router = useRouter()
7
+
8
+ if (isServer ?? router.isServer) {
9
+ return router.stores.location.state.state.__TSR_index !== 0
10
+ }
11
+
12
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
13
+ return useStore(
14
+ router.stores.location,
15
+ (location) => location.state.__TSR_index !== 0,
16
+ )
5
17
  }
@@ -1,4 +1,8 @@
1
- import { useRouterState } from './useRouterState'
1
+ import { useStore } from '@tanstack/react-store'
2
+ import { useRef } from 'react'
3
+ import { replaceEqualDeep } from '@tanstack/router-core'
4
+ import { isServer } from '@tanstack/router-core/isServer'
5
+ import { useRouter } from './useRouter'
2
6
  import type {
3
7
  StructuralSharingOption,
4
8
  ValidateSelected,
@@ -45,8 +49,31 @@ export function useLocation<
45
49
  opts?: UseLocationBaseOptions<TRouter, TSelected, TStructuralSharing> &
46
50
  StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
47
51
  ): UseLocationResult<TRouter, TSelected> {
48
- return useRouterState({
49
- select: (state: any) =>
50
- opts?.select ? opts.select(state.location) : state.location,
51
- } as any) as UseLocationResult<TRouter, TSelected>
52
+ const router = useRouter<TRouter>()
53
+
54
+ if (isServer ?? router.isServer) {
55
+ const location = router.stores.location.state
56
+ return (
57
+ opts?.select ? opts.select(location as any) : location
58
+ ) as UseLocationResult<TRouter, TSelected>
59
+ }
60
+
61
+ const previousResult =
62
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
63
+ useRef<ValidateSelected<TRouter, TSelected, TStructuralSharing>>(undefined)
64
+
65
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
66
+ return useStore(router.stores.location, (location) => {
67
+ const selected = (
68
+ opts?.select ? opts.select(location as any) : location
69
+ ) as ValidateSelected<TRouter, TSelected, TStructuralSharing>
70
+
71
+ if (opts?.structuralSharing ?? router.options.defaultStructuralSharing) {
72
+ const shared = replaceEqualDeep(previousResult.current, selected)
73
+ previousResult.current = shared
74
+ return shared
75
+ }
76
+
77
+ return selected
78
+ }) as UseLocationResult<TRouter, TSelected>
52
79
  }
package/src/useMatch.tsx CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as React from 'react'
2
- import invariant from 'tiny-invariant'
3
- import { useRouterState } from './useRouterState'
2
+ import { useStore } from '@tanstack/react-store'
3
+ import { invariant, replaceEqualDeep } from '@tanstack/router-core'
4
+ import { isServer } from '@tanstack/router-core/isServer'
4
5
  import { dummyMatchContext, matchContext } from './matchContext'
6
+ import { useRouter } from './useRouter'
5
7
  import type {
6
8
  StructuralSharingOption,
7
9
  ValidateSelected,
@@ -16,6 +18,12 @@ import type {
16
18
  ThrowOrOptional,
17
19
  } from '@tanstack/router-core'
18
20
 
21
+ const dummyStore = {
22
+ state: undefined,
23
+ get: () => undefined,
24
+ subscribe: () => () => {},
25
+ } as any
26
+
19
27
  export interface UseMatchBaseOptions<
20
28
  TRouter extends AnyRouter,
21
29
  TFrom,
@@ -96,28 +104,69 @@ export function useMatch<
96
104
  TStructuralSharing
97
105
  >,
98
106
  ): ThrowOrOptional<UseMatchResult<TRouter, TFrom, TStrict, TSelected>, TThrow> {
107
+ const router = useRouter<TRouter>()
99
108
  const nearestMatchId = React.useContext(
100
109
  opts.from ? dummyMatchContext : matchContext,
101
110
  )
102
111
 
103
- const matchSelection = useRouterState({
104
- select: (state: any) => {
105
- const match = state.matches.find((d: any) =>
106
- opts.from ? opts.from === d.routeId : d.id === nearestMatchId,
107
- )
108
- invariant(
109
- !((opts.shouldThrow ?? true) && !match),
110
- `Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
111
- )
112
-
113
- if (match === undefined) {
114
- return undefined
112
+ const key = opts.from ?? nearestMatchId
113
+ const matchStore = key
114
+ ? opts.from
115
+ ? router.stores.getMatchStoreByRouteId(key)
116
+ : router.stores.activeMatchStoresById.get(key)
117
+ : undefined
118
+
119
+ if (isServer ?? router.isServer) {
120
+ const match = matchStore?.state
121
+ if ((opts.shouldThrow ?? true) && !match) {
122
+ if (process.env.NODE_ENV !== 'production') {
123
+ throw new Error(
124
+ `Invariant failed: Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
125
+ )
115
126
  }
116
127
 
117
- return opts.select ? opts.select(match) : match
118
- },
119
- structuralSharing: opts.structuralSharing,
120
- } as any)
128
+ invariant()
129
+ }
130
+
131
+ if (match === undefined) {
132
+ return undefined as any
133
+ }
134
+
135
+ return (opts.select ? opts.select(match as any) : match) as any
136
+ }
137
+
138
+ const previousResult =
139
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
140
+ React.useRef<ValidateSelected<TRouter, TSelected, TStructuralSharing>>(
141
+ undefined,
142
+ )
143
+
144
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static
145
+ return useStore(matchStore ?? dummyStore, (match) => {
146
+ if ((opts.shouldThrow ?? true) && !match) {
147
+ if (process.env.NODE_ENV !== 'production') {
148
+ throw new Error(
149
+ `Invariant failed: Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
150
+ )
151
+ }
152
+
153
+ invariant()
154
+ }
155
+
156
+ if (match === undefined) {
157
+ return undefined
158
+ }
159
+
160
+ const selected = (
161
+ opts.select ? opts.select(match as any) : match
162
+ ) as ValidateSelected<TRouter, TSelected, TStructuralSharing>
163
+
164
+ if (opts.structuralSharing ?? router.options.defaultStructuralSharing) {
165
+ const shared = replaceEqualDeep(previousResult.current, selected)
166
+ previousResult.current = shared
167
+ return shared
168
+ }
121
169
 
122
- return matchSelection as any
170
+ return selected
171
+ }) as any
123
172
  }
package/src/useRouter.tsx CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as React from 'react'
2
- import warning from 'tiny-warning'
3
2
  import { routerContext } from './routerContext'
4
3
  import type { AnyRouter, RegisteredRouter } from '@tanstack/router-core'
5
4
 
@@ -17,9 +16,12 @@ export function useRouter<TRouter extends AnyRouter = RegisteredRouter>(opts?: {
17
16
  warn?: boolean
18
17
  }): TRouter {
19
18
  const value = React.useContext(routerContext)
20
- warning(
21
- !((opts?.warn ?? true) && !value),
22
- 'useRouter must be used inside a <RouterProvider> component!',
23
- )
19
+ if (process.env.NODE_ENV !== 'production') {
20
+ if ((opts?.warn ?? true) && !value) {
21
+ console.warn(
22
+ 'Warning: useRouter must be used inside a <RouterProvider> component!',
23
+ )
24
+ }
25
+ }
24
26
  return value as any
25
27
  }
@@ -57,7 +57,9 @@ export function useRouterState<
57
57
  // Avoid subscribing to the store (and any structural sharing work) on the server.
58
58
  const _isServer = isServer ?? router.isServer
59
59
  if (_isServer) {
60
- const state = router.state as RouterState<TRouter['routeTree']>
60
+ const state = router.stores.__store.state as RouterState<
61
+ TRouter['routeTree']
62
+ >
61
63
  return (opts?.select ? opts.select(state) : state) as UseRouterStateResult<
62
64
  TRouter,
63
65
  TSelected
@@ -69,7 +71,7 @@ export function useRouterState<
69
71
  useRef<ValidateSelected<TRouter, TSelected, TStructuralSharing>>(undefined)
70
72
 
71
73
  // eslint-disable-next-line react-hooks/rules-of-hooks
72
- return useStore(router.__store, (state) => {
74
+ return useStore(router.stores.__store, (state) => {
73
75
  if (opts?.select) {
74
76
  if (opts.structuralSharing ?? router.options.defaultStructuralSharing) {
75
77
  const newSlice = replaceEqualDeep(