@tanstack/vue-router 1.167.5 → 1.168.1

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 (95) hide show
  1. package/dist/esm/Match.js +55 -61
  2. package/dist/esm/Match.js.map +1 -1
  3. package/dist/esm/Matches.js +8 -15
  4. package/dist/esm/Matches.js.map +1 -1
  5. package/dist/esm/Scripts.js +7 -6
  6. package/dist/esm/Scripts.js.map +1 -1
  7. package/dist/esm/Transitioner.js +18 -24
  8. package/dist/esm/Transitioner.js.map +1 -1
  9. package/dist/esm/headContentUtils.js +13 -15
  10. package/dist/esm/headContentUtils.js.map +1 -1
  11. package/dist/esm/index.dev.js +6 -6
  12. package/dist/esm/index.js +6 -6
  13. package/dist/esm/link.js +242 -178
  14. package/dist/esm/link.js.map +1 -1
  15. package/dist/esm/matchContext.d.ts +8 -14
  16. package/dist/esm/matchContext.js +11 -9
  17. package/dist/esm/matchContext.js.map +1 -1
  18. package/dist/esm/not-found.js +6 -3
  19. package/dist/esm/not-found.js.map +1 -1
  20. package/dist/esm/router.js +2 -1
  21. package/dist/esm/router.js.map +1 -1
  22. package/dist/esm/routerStores.d.ts +13 -0
  23. package/dist/esm/routerStores.js +33 -0
  24. package/dist/esm/routerStores.js.map +1 -0
  25. package/dist/esm/ssr/RouterClient.js +1 -1
  26. package/dist/esm/ssr/RouterClient.js.map +1 -1
  27. package/dist/esm/ssr/renderRouterToStream.js +2 -2
  28. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  29. package/dist/esm/ssr/renderRouterToString.js +1 -1
  30. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  31. package/dist/esm/useCanGoBack.d.ts +1 -1
  32. package/dist/esm/useCanGoBack.js +3 -2
  33. package/dist/esm/useCanGoBack.js.map +1 -1
  34. package/dist/esm/useLocation.js +3 -2
  35. package/dist/esm/useLocation.js.map +1 -1
  36. package/dist/esm/useMatch.js +29 -19
  37. package/dist/esm/useMatch.js.map +1 -1
  38. package/dist/esm/useRouterState.js +4 -4
  39. package/dist/esm/useRouterState.js.map +1 -1
  40. package/dist/source/Match.jsx +121 -159
  41. package/dist/source/Match.jsx.map +1 -1
  42. package/dist/source/Matches.jsx +11 -28
  43. package/dist/source/Matches.jsx.map +1 -1
  44. package/dist/source/Scripts.jsx +32 -35
  45. package/dist/source/Scripts.jsx.map +1 -1
  46. package/dist/source/Transitioner.jsx +19 -21
  47. package/dist/source/Transitioner.jsx.map +1 -1
  48. package/dist/source/headContentUtils.jsx +51 -61
  49. package/dist/source/headContentUtils.jsx.map +1 -1
  50. package/dist/source/link.jsx +298 -249
  51. package/dist/source/link.jsx.map +1 -1
  52. package/dist/source/matchContext.d.ts +8 -14
  53. package/dist/source/matchContext.jsx +17 -23
  54. package/dist/source/matchContext.jsx.map +1 -1
  55. package/dist/source/not-found.jsx +6 -5
  56. package/dist/source/not-found.jsx.map +1 -1
  57. package/dist/source/router.js +2 -1
  58. package/dist/source/router.js.map +1 -1
  59. package/dist/source/routerStores.d.ts +13 -0
  60. package/dist/source/routerStores.js +37 -0
  61. package/dist/source/routerStores.js.map +1 -0
  62. package/dist/source/ssr/RouterClient.jsx +1 -1
  63. package/dist/source/ssr/RouterClient.jsx.map +1 -1
  64. package/dist/source/ssr/renderRouterToStream.jsx +2 -2
  65. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
  66. package/dist/source/ssr/renderRouterToString.jsx +1 -1
  67. package/dist/source/ssr/renderRouterToString.jsx.map +1 -1
  68. package/dist/source/useCanGoBack.d.ts +1 -1
  69. package/dist/source/useCanGoBack.js +4 -2
  70. package/dist/source/useCanGoBack.js.map +1 -1
  71. package/dist/source/useLocation.jsx +4 -4
  72. package/dist/source/useLocation.jsx.map +1 -1
  73. package/dist/source/useMatch.jsx +60 -38
  74. package/dist/source/useMatch.jsx.map +1 -1
  75. package/dist/source/useRouterState.jsx +4 -4
  76. package/dist/source/useRouterState.jsx.map +1 -1
  77. package/package.json +4 -4
  78. package/skills/vue-router/SKILL.md +3 -0
  79. package/src/Match.tsx +168 -180
  80. package/src/Matches.tsx +18 -31
  81. package/src/Scripts.tsx +40 -40
  82. package/src/Transitioner.tsx +35 -23
  83. package/src/headContentUtils.tsx +101 -107
  84. package/src/link.tsx +445 -300
  85. package/src/matchContext.tsx +23 -25
  86. package/src/not-found.tsx +9 -5
  87. package/src/router.ts +2 -1
  88. package/src/routerStores.ts +54 -0
  89. package/src/ssr/RouterClient.tsx +1 -1
  90. package/src/ssr/renderRouterToStream.tsx +2 -2
  91. package/src/ssr/renderRouterToString.tsx +1 -1
  92. package/src/useCanGoBack.ts +7 -2
  93. package/src/useLocation.tsx +8 -5
  94. package/src/useMatch.tsx +95 -49
  95. package/src/useRouterState.tsx +6 -4
package/src/Match.tsx CHANGED
@@ -9,12 +9,16 @@ import {
9
9
  rootRouteId,
10
10
  } from '@tanstack/router-core'
11
11
  import { isServer } from '@tanstack/router-core/isServer'
12
+ import { useStore } from '@tanstack/vue-store'
12
13
  import { CatchBoundary, ErrorComponent } from './CatchBoundary'
13
14
  import { ClientOnly } from './ClientOnly'
14
- import { useRouterState } from './useRouterState'
15
15
  import { useRouter } from './useRouter'
16
16
  import { CatchNotFound } from './not-found'
17
- import { matchContext } from './matchContext'
17
+ import {
18
+ matchContext,
19
+ pendingMatchContext,
20
+ routeIdContext,
21
+ } from './matchContext'
18
22
  import { renderRouteNotFound } from './renderRouteNotFound'
19
23
  import { ScrollRestoration } from './scroll-restoration'
20
24
  import type { VNode } from 'vue'
@@ -31,55 +35,52 @@ export const Match = Vue.defineComponent({
31
35
  setup(props) {
32
36
  const router = useRouter()
33
37
 
34
- // Track the last known routeId to handle stale props during same-route transitions
35
- let lastKnownRouteId: string | null = null
36
-
37
- // Combined selector that returns all needed data including the actual matchId
38
- // This handles stale props.matchId during same-route transitions
39
- const matchData = useRouterState({
40
- select: (s) => {
41
- // First try to find match by props.matchId
42
- let match = s.matches.find((d) => d.id === props.matchId)
43
- let matchIndex = match
44
- ? s.matches.findIndex((d) => d.id === props.matchId)
45
- : -1
46
-
47
- // If match found, update lastKnownRouteId
48
- if (match) {
49
- lastKnownRouteId = match.routeId as string
50
- } else if (lastKnownRouteId) {
51
- // Match not found - props.matchId might be stale during a same-route transition
52
- // Try to find the NEW match by routeId
53
- match = s.matches.find((d) => d.routeId === lastKnownRouteId)
54
- matchIndex = match
55
- ? s.matches.findIndex((d) => d.routeId === lastKnownRouteId)
56
- : -1
57
- }
58
-
59
- if (!match) {
60
- return null
61
- }
62
-
63
- const routeId = match.routeId as string
64
- const parentRouteId =
65
- matchIndex > 0 ? (s.matches[matchIndex - 1]?.routeId as string) : null
66
-
67
- return {
68
- matchId: match.id, // Return the actual matchId (may differ from props.matchId)
69
- routeId,
70
- parentRouteId,
71
- loadedAt: s.loadedAt,
72
- ssr: match.ssr,
73
- _displayPending: match._displayPending,
74
- }
75
- },
76
- })
38
+ // Derive routeId from initial props.matchId stable for this component's
39
+ // lifetime. The routeId never changes for a given route position in the
40
+ // tree, even when matchId changes (loaderDepsHash, etc).
41
+ const routeId = router.stores.activeMatchStoresById.get(
42
+ props.matchId,
43
+ )?.routeId
77
44
 
78
45
  invariant(
79
- matchData.value,
46
+ routeId,
80
47
  `Could not find routeId for matchId "${props.matchId}". Please file an issue!`,
81
48
  )
82
49
 
50
+ // Static route-tree check: is this route a direct child of the root?
51
+ // parentRoute is set at build time, so no reactive tracking needed.
52
+ const isChildOfRoot =
53
+ (router.routesById[routeId] as AnyRoute)?.parentRoute?.id === rootRouteId
54
+
55
+ // Single stable store subscription — getMatchStoreByRouteId returns a
56
+ // cached computed store that resolves routeId → current match state
57
+ // through the signal graph. No bridge needed.
58
+ const activeMatch = useStore(
59
+ router.stores.getMatchStoreByRouteId(routeId),
60
+ (value) => value,
61
+ )
62
+ const isPendingMatchRef = useStore(
63
+ router.stores.pendingRouteIds,
64
+ (pendingRouteIds) => Boolean(pendingRouteIds[routeId]),
65
+ { equal: Object.is },
66
+ )
67
+ const loadedAt = useStore(router.stores.loadedAt, (value) => value)
68
+
69
+ const matchData = Vue.computed(() => {
70
+ const match = activeMatch.value
71
+ if (!match) {
72
+ return null
73
+ }
74
+
75
+ return {
76
+ matchId: match.id,
77
+ routeId,
78
+ loadedAt: loadedAt.value,
79
+ ssr: match.ssr,
80
+ _displayPending: match._displayPending,
81
+ }
82
+ })
83
+
83
84
  const route = Vue.computed(() =>
84
85
  matchData.value ? router.routesById[matchData.value.routeId] : null,
85
86
  )
@@ -123,27 +124,20 @@ export const Match = Vue.defineComponent({
123
124
  : null,
124
125
  )
125
126
 
126
- // Create a ref for the current matchId that we provide to child components
127
- // This ref is updated to the ACTUAL matchId found (which may differ from props during transitions)
128
- const matchIdRef = Vue.ref(matchData.value?.matchId ?? props.matchId)
127
+ // Provide routeId context (stable string) for children.
128
+ // MatchInner, Outlet, and useMatch all consume this.
129
+ Vue.provide(routeIdContext, routeId)
129
130
 
130
- // Watch both props.matchId and matchData to keep matchIdRef in sync
131
- // This ensures Outlet gets the correct matchId even during transitions
132
- Vue.watch(
133
- [() => props.matchId, () => matchData.value?.matchId],
134
- ([propsMatchId, dataMatchId]) => {
135
- // Prefer the matchId from matchData (which handles fallback)
136
- // Fall back to props.matchId if matchData is null
137
- matchIdRef.value = dataMatchId ?? propsMatchId
138
- },
139
- { immediate: true },
131
+ // Provide reactive nearest-match context for hooks that slice the active
132
+ // matches array relative to the current match.
133
+ const matchIdRef = Vue.computed(
134
+ () => activeMatch.value?.id ?? props.matchId,
140
135
  )
141
-
142
- // Provide the matchId to child components
143
136
  Vue.provide(matchContext, matchIdRef)
144
137
 
138
+ Vue.provide(pendingMatchContext, isPendingMatchRef)
139
+
145
140
  return (): VNode => {
146
- // Use the actual matchId from matchData, not props (which may be stale)
147
141
  const actualMatchId = matchData.value?.matchId ?? props.matchId
148
142
 
149
143
  const resolvedNoSsr =
@@ -203,8 +197,7 @@ export const Match = Vue.defineComponent({
203
197
  // Add scroll restoration if needed
204
198
  const withScrollRestoration: Array<VNode> = [
205
199
  content,
206
- matchData.value?.parentRouteId === rootRouteId &&
207
- router.options.scrollRestoration
200
+ isChildOfRoot && router.options.scrollRestoration
208
201
  ? Vue.h(Vue.Fragment, null, [
209
202
  Vue.h(OnRendered),
210
203
  Vue.h(ScrollRestoration),
@@ -236,7 +229,7 @@ export const Match = Vue.defineComponent({
236
229
  // On Rendered can't happen above the root layout because it actually
237
230
  // renders a dummy dom element to track the rendered state of the app.
238
231
  // We render a script tag with a key that changes based on the current
239
- // location state.key. Also, because it's below the root layout, it
232
+ // location state.__TSR_key. Also, because it's below the root layout, it
240
233
  // allows us to fire onRendered events even after a hydration mismatch
241
234
  // error that occurred above the root layout (like bad head/link tags,
242
235
  // which is common).
@@ -245,20 +238,32 @@ const OnRendered = Vue.defineComponent({
245
238
  setup() {
246
239
  const router = useRouter()
247
240
 
248
- const location = useRouterState({
249
- select: (s) => {
250
- return s.resolvedLocation?.state.key
251
- },
252
- })
241
+ const location = useStore(
242
+ router.stores.resolvedLocation,
243
+ (resolvedLocation) => resolvedLocation?.state.__TSR_key,
244
+ )
253
245
 
254
- Vue.watchEffect(() => {
255
- if (location.value) {
256
- router.emit({
257
- type: 'onRendered',
258
- ...getLocationChangeInfo(router.state),
259
- })
260
- }
261
- })
246
+ let prevHref: string | undefined
247
+
248
+ Vue.watch(
249
+ location,
250
+ () => {
251
+ if (location.value) {
252
+ const currentHref = router.latestLocation.href
253
+ if (prevHref === undefined || prevHref !== currentHref) {
254
+ router.emit({
255
+ type: 'onRendered',
256
+ ...getLocationChangeInfo(
257
+ router.stores.location.state,
258
+ router.stores.resolvedLocation.state,
259
+ ),
260
+ })
261
+ prevHref = currentHref
262
+ }
263
+ }
264
+ },
265
+ { immediate: true },
266
+ )
262
267
 
263
268
  return () => null
264
269
  },
@@ -275,68 +280,52 @@ export const MatchInner = Vue.defineComponent({
275
280
  setup(props) {
276
281
  const router = useRouter()
277
282
 
278
- // Track the last known routeId to handle stale props during same-route transitions
279
- // This is stored outside the selector so it persists across selector calls
280
- let lastKnownRouteId: string | null = null
283
+ // Use routeId from context (provided by parent Match) stable string.
284
+ const routeId = Vue.inject(routeIdContext)!
285
+ const activeMatch = useStore(
286
+ router.stores.getMatchStoreByRouteId(routeId),
287
+ (value) => value,
288
+ )
281
289
 
282
290
  // Combined selector for match state AND remount key
283
291
  // This ensures both are computed in the same selector call with consistent data
284
- const combinedState = useRouterState({
285
- select: (s) => {
286
- // First try to find match by props.matchId
287
- let match = s.matches.find((d) => d.id === props.matchId)
288
-
289
- // If match found, update lastKnownRouteId
290
- if (match) {
291
- lastKnownRouteId = match.routeId as string
292
- } else if (lastKnownRouteId) {
293
- // Match not found - props.matchId might be stale during a same-route transition
294
- // (matchId changed due to loaderDepsHash but props haven't updated yet)
295
- // Try to find the NEW match by routeId and use that instead
296
- const sameRouteMatch = s.matches.find(
297
- (d) => d.routeId === lastKnownRouteId,
298
- )
299
- if (sameRouteMatch) {
300
- match = sameRouteMatch
301
- }
302
- }
303
-
304
- if (!match) {
305
- // Route no longer exists - truly navigating away
306
- return null
307
- }
292
+ const combinedState = Vue.computed(() => {
293
+ const match = activeMatch.value
294
+ if (!match) {
295
+ // Route no longer exists - truly navigating away
296
+ return null
297
+ }
308
298
 
309
- const routeId = match.routeId as string
299
+ const matchRouteId = match.routeId as string
310
300
 
311
- // Compute remount key
312
- const remountFn =
313
- (router.routesById[routeId] as AnyRoute).options.remountDeps ??
314
- router.options.defaultRemountDeps
301
+ // Compute remount key
302
+ const remountFn =
303
+ (router.routesById[matchRouteId] as AnyRoute).options.remountDeps ??
304
+ router.options.defaultRemountDeps
315
305
 
316
- let remountKey: string | undefined
317
- if (remountFn) {
318
- const remountDeps = remountFn({
319
- routeId,
320
- loaderDeps: match.loaderDeps,
321
- params: match._strictParams,
322
- search: match._strictSearch,
323
- })
324
- remountKey = remountDeps ? JSON.stringify(remountDeps) : undefined
325
- }
306
+ let remountKey: string | undefined
307
+ if (remountFn) {
308
+ const remountDeps = remountFn({
309
+ routeId: matchRouteId,
310
+ loaderDeps: match.loaderDeps,
311
+ params: match._strictParams,
312
+ search: match._strictSearch,
313
+ })
314
+ remountKey = remountDeps ? JSON.stringify(remountDeps) : undefined
315
+ }
326
316
 
327
- return {
328
- routeId,
329
- match: {
330
- id: match.id,
331
- status: match.status,
332
- error: match.error,
333
- ssr: match.ssr,
334
- _forcePending: match._forcePending,
335
- _displayPending: match._displayPending,
336
- },
337
- remountKey,
338
- }
339
- },
317
+ return {
318
+ routeId: matchRouteId,
319
+ match: {
320
+ id: match.id,
321
+ status: match.status,
322
+ error: match.error,
323
+ ssr: match.ssr,
324
+ _forcePending: match._forcePending,
325
+ _displayPending: match._displayPending,
326
+ },
327
+ remountKey,
328
+ }
340
329
  })
341
330
 
342
331
  const route = Vue.computed(() => {
@@ -460,49 +449,56 @@ export const Outlet = Vue.defineComponent({
460
449
  name: 'Outlet',
461
450
  setup() {
462
451
  const router = useRouter()
463
- const matchId = Vue.inject(matchContext)
464
- const safeMatchId = Vue.computed(() => matchId?.value || '')
452
+ const parentRouteId = Vue.inject(routeIdContext)
465
453
 
466
- const routeId = useRouterState({
467
- select: (s) =>
468
- s.matches.find((d) => d.id === safeMatchId.value)?.routeId as string,
469
- })
454
+ if (!parentRouteId) {
455
+ return (): VNode | null => null
456
+ }
470
457
 
471
- const route = Vue.computed(() => router.routesById[routeId.value]!)
458
+ // Parent state via stable routeId store — single subscription
459
+ const parentMatch = useStore(
460
+ router.stores.getMatchStoreByRouteId(parentRouteId),
461
+ (v) => v,
462
+ )
472
463
 
473
- const parentGlobalNotFound = useRouterState({
474
- select: (s) => {
475
- const matches = s.matches
476
- const parentMatch = matches.find((d) => d.id === safeMatchId.value)
464
+ const route = Vue.computed(() =>
465
+ parentMatch.value
466
+ ? router.routesById[parentMatch.value.routeId as string]!
467
+ : undefined,
468
+ )
477
469
 
478
- // During navigation transitions, parent match can be temporarily removed
479
- // Return false to avoid errors - the component will handle this gracefully
480
- if (!parentMatch) {
481
- return false
482
- }
470
+ const parentGlobalNotFound = Vue.computed(
471
+ () => parentMatch.value?.globalNotFound ?? false,
472
+ )
483
473
 
484
- return parentMatch.globalNotFound
485
- },
486
- })
474
+ // Child match lookup: read the child matchId from the shared derived
475
+ // map (one reactive node for the whole tree), then grab match state
476
+ // directly from the pool.
477
+ const childMatchIdMap = useStore(
478
+ router.stores.childMatchIdByRouteId,
479
+ (v) => v,
480
+ )
487
481
 
488
- const childMatchData = useRouterState({
489
- select: (s) => {
490
- const matches = s.matches
491
- const index = matches.findIndex((d) => d.id === safeMatchId.value)
492
- const child = matches[index + 1]
493
- if (!child) return null
494
- return {
495
- id: child.id,
496
- // Key based on routeId + params only (not loaderDeps)
497
- // This ensures component recreates when params change,
498
- // but NOT when only loaderDeps change
499
- paramsKey: child.routeId + JSON.stringify(child._strictParams),
500
- }
501
- },
482
+ const childMatchData = Vue.computed(() => {
483
+ const childId = childMatchIdMap.value[parentRouteId]
484
+ if (!childId) return null
485
+ const child = router.stores.activeMatchStoresById.get(childId)?.state
486
+ if (!child) return null
487
+
488
+ return {
489
+ id: child.id,
490
+ // Key based on routeId + params only (not loaderDeps)
491
+ // This ensures component recreates when params change,
492
+ // but NOT when only loaderDeps change
493
+ paramsKey: child.routeId + JSON.stringify(child._strictParams),
494
+ }
502
495
  })
503
496
 
504
497
  return (): VNode | null => {
505
498
  if (parentGlobalNotFound.value) {
499
+ if (!route.value) {
500
+ return null
501
+ }
506
502
  return renderRouteNotFound(router, route.value, undefined)
507
503
  }
508
504
 
@@ -515,20 +511,12 @@ export const Outlet = Vue.defineComponent({
515
511
  key: childMatchData.value.paramsKey,
516
512
  })
517
513
 
518
- if (safeMatchId.value === rootRouteId) {
519
- return Vue.h(
520
- Vue.Suspense,
521
- {
522
- fallback: router.options.defaultPendingComponent
523
- ? Vue.h(router.options.defaultPendingComponent)
524
- : null,
525
- },
526
- {
527
- default: () => nextMatch,
528
- },
529
- )
530
- }
531
-
514
+ // Note: We intentionally do NOT wrap in Suspense here.
515
+ // The top-level Suspense in Matches already covers the root.
516
+ // The old code compared matchId (e.g. "__root__/") with rootRouteId ("__root__")
517
+ // which never matched, so this Suspense was effectively dead code.
518
+ // With routeId-based lookup, parentRouteId === rootRouteId would match,
519
+ // causing a double-Suspense that corrupts Vue's DOM during updates.
532
520
  return nextMatch
533
521
  }
534
522
  },
package/src/Matches.tsx CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as Vue from 'vue'
2
2
  import warning from 'tiny-warning'
3
3
  import { isServer } from '@tanstack/router-core/isServer'
4
+ import { useStore } from '@tanstack/vue-store'
4
5
  import { CatchBoundary } from './CatchBoundary'
5
- import { useRouterState } from './useRouterState'
6
6
  import { useRouter } from './useRouter'
7
7
  import { useTransitionerSetup } from './Transitioner'
8
8
  import { matchContext } from './matchContext'
@@ -21,7 +21,6 @@ import type {
21
21
  ResolveRelativePath,
22
22
  ResolveRoute,
23
23
  RouteByPath,
24
- RouterState,
25
24
  ToSubOptionsProps,
26
25
  } from '@tanstack/router-core'
27
26
 
@@ -104,15 +103,8 @@ const MatchesInner = Vue.defineComponent({
104
103
  setup() {
105
104
  const router = useRouter()
106
105
 
107
- const matchId = useRouterState({
108
- select: (s) => {
109
- return s.matches[0]?.id
110
- },
111
- })
112
-
113
- const resetKey = useRouterState({
114
- select: (s) => s.loadedAt,
115
- })
106
+ const matchId = useStore(router.stores.firstMatchId, (id) => id)
107
+ const resetKey = useStore(router.stores.loadedAt, (loadedAt) => loadedAt)
116
108
 
117
109
  // Create a ref for the match id to provide
118
110
  const matchIdRef = Vue.computed(() => matchId.value)
@@ -165,15 +157,10 @@ export type UseMatchRouteOptions<
165
157
  export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
166
158
  const router = useRouter()
167
159
 
168
- // Track state changes to trigger re-computation
169
- // Use multiple state values like React does for complete reactivity
170
- const routerState = useRouterState({
171
- select: (s) => ({
172
- locationHref: s.location.href,
173
- resolvedLocationHref: s.resolvedLocation?.href,
174
- status: s.status,
175
- }),
176
- })
160
+ const routerState = useStore(
161
+ router.stores.matchRouteReactivity,
162
+ (value) => value,
163
+ )
177
164
 
178
165
  return <
179
166
  const TFrom extends string = string,
@@ -272,9 +259,11 @@ export const MatchRoute = Vue.defineComponent({
272
259
  },
273
260
  },
274
261
  setup(props, { slots }) {
275
- const status = useRouterState({
276
- select: (s) => s.status,
277
- })
262
+ const router = useRouter()
263
+ const status = useStore(
264
+ router.stores.matchRouteReactivity,
265
+ (value) => value.status,
266
+ )
278
267
 
279
268
  return () => {
280
269
  if (!status.value) return null
@@ -314,14 +303,12 @@ export function useMatches<
314
303
  >(
315
304
  opts?: UseMatchesBaseOptions<TRouter, TSelected>,
316
305
  ): Vue.Ref<UseMatchesResult<TRouter, TSelected>> {
317
- return useRouterState({
318
- select: (state: RouterState<TRouter['routeTree']>) => {
319
- const matches = state?.matches || []
320
- return opts?.select
321
- ? opts.select(matches as Array<MakeRouteMatchUnion<TRouter>>)
322
- : matches
323
- },
324
- } as any) as Vue.Ref<UseMatchesResult<TRouter, TSelected>>
306
+ const router = useRouter<TRouter>()
307
+ return useStore(router.stores.activeMatchesSnapshot, (matches) => {
308
+ return opts?.select
309
+ ? opts.select(matches as Array<MakeRouteMatchUnion<TRouter>>)
310
+ : (matches as any)
311
+ })
325
312
  }
326
313
 
327
314
  export function useParentMatches<
package/src/Scripts.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as Vue from 'vue'
2
+ import { useStore } from '@tanstack/vue-store'
2
3
  import { Asset } from './Asset'
3
- import { useRouterState } from './useRouterState'
4
4
  import { useRouter } from './useRouter'
5
5
  import type { RouterManagedTag } from '@tanstack/router-core'
6
6
 
@@ -9,51 +9,51 @@ export const Scripts = Vue.defineComponent({
9
9
  setup() {
10
10
  const router = useRouter()
11
11
  const nonce = router.options.ssr?.nonce
12
+ const matches = useStore(
13
+ router.stores.activeMatchesSnapshot,
14
+ (value) => value,
15
+ )
12
16
 
13
- const assetScripts = useRouterState({
14
- select: (state) => {
15
- const assetScripts: Array<RouterManagedTag> = []
16
- const manifest = router.ssr?.manifest
17
+ const assetScripts = Vue.computed<Array<RouterManagedTag>>(() => {
18
+ const assetScripts: Array<RouterManagedTag> = []
19
+ const manifest = router.ssr?.manifest
17
20
 
18
- if (!manifest) {
19
- return []
20
- }
21
+ if (!manifest) {
22
+ return []
23
+ }
21
24
 
22
- state.matches
23
- .map((match) => router.looseRoutesById[match.routeId]!)
24
- .forEach((route) =>
25
- manifest.routes[route.id]?.assets
26
- ?.filter((d) => d.tag === 'script')
27
- .forEach((asset) => {
28
- assetScripts.push({
29
- tag: 'script',
30
- attrs: { ...asset.attrs, nonce },
31
- children: asset.children,
32
- } as RouterManagedTag)
33
- }),
34
- )
25
+ matches.value
26
+ .map((match) => router.looseRoutesById[match.routeId]!)
27
+ .forEach((route) =>
28
+ manifest.routes[route.id]?.assets
29
+ ?.filter((d) => d.tag === 'script')
30
+ .forEach((asset) => {
31
+ assetScripts.push({
32
+ tag: 'script',
33
+ attrs: { ...asset.attrs, nonce },
34
+ children: asset.children,
35
+ } as RouterManagedTag)
36
+ }),
37
+ )
35
38
 
36
- return assetScripts
37
- },
39
+ return assetScripts
38
40
  })
39
41
 
40
- const scripts = useRouterState({
41
- select: (state) => ({
42
- scripts: (
43
- state.matches
44
- .map((match) => match.scripts!)
45
- .flat(1)
46
- .filter(Boolean) as Array<RouterManagedTag>
47
- ).map(({ children, ...script }) => ({
48
- tag: 'script' as const,
49
- attrs: {
50
- ...script,
51
- nonce,
52
- },
53
- children,
54
- })),
55
- }),
56
- })
42
+ const scripts = Vue.computed(() => ({
43
+ scripts: (
44
+ matches.value
45
+ .map((match) => match.scripts!)
46
+ .flat(1)
47
+ .filter(Boolean) as Array<RouterManagedTag>
48
+ ).map(({ children, ...script }) => ({
49
+ tag: 'script' as const,
50
+ attrs: {
51
+ ...script,
52
+ nonce,
53
+ },
54
+ children,
55
+ })),
56
+ }))
57
57
 
58
58
  const mounted = Vue.ref(false)
59
59
  Vue.onMounted(() => {