Which project does this relate to?
Router
Describe the bug
Since the granular stores refactor there is no public reactive way to read pendingMatches. It used to be part of RouterState, so you could do useRouterState({ select: (s) => s.pendingMatches }) (older issues like #925 quote this.state.pendingMatches directly). Now RouterState no longer carries matches/pendingMatches, and useMatches only selects over the committed matches. The data still exists in router.stores.pendingMatches, but no hook exposes it, so this looks like API surface that fell off in the refactor rather than a deliberate removal.
Why I care: I'm migrating a fairly big app from react-router, and our tab strips light up the clicked tab the moment a navigation starts instead of when the target's chunk and loader have resolved. Everything needed for that is already in the router, since the pending matches are built synchronously at navigation start and carry the target route's staticData (each route declares which tab it lights up there). I just can't get at them reactively.
The workaround I ended up with works but is not pretty. It reaches into router.stores and leans on event ordering (the pending stores happen to be populated before onBeforeLoad is emitted, and committed before onResolved):
function useActiveTab() {
const router = useRouter()
const subscribe = useCallback(
(onChange: () => void) => {
const subs = [
router.subscribe('onBeforeLoad', onChange),
router.subscribe('onResolved', onChange),
]
return () => subs.forEach((unsub) => unsub())
},
[router],
)
return useSyncExternalStore(subscribe, () => {
const pending = router.stores.pendingMatches.get()
const matches = pending.length > 0 ? pending : router.stores.matches.get()
return matches.findLast((m) => m.staticData.tab)?.staticData.tab
})
}
I'd love either a usePendingMatches hook mirroring useMatches (select + structural sharing), or pendingMatches back on the state that useRouterState selects over. Happy to take a stab at a PR if you can point me at the preferred shape.
Complete minimal reproducer
Not really applicable (missing API rather than wrong behavior), but here is the gap in one snippet:
// worked before the stores refactor:
const pending = useRouterState({ select: (s) => s.pendingMatches })
// today: Property 'pendingMatches' does not exist on type 'RouterState<...>'
Steps to Reproduce the Bug
useRouterState({ select: (s) => s.pendingMatches }) no longer type-checks (and the value is not on the state object)
useMatches only covers committed matches
- There is no other exported hook that reads
router.stores.pendingMatches
Expected behavior
Some supported reactive way to read the pending matches, like usePendingMatches({ select }) or pendingMatches on RouterState again.
Platform
- @tanstack/react-router 1.170.18 / @tanstack/router-core 1.171.15
- React 19, Vite
Additional context
Filing this as a bug rather than a discussion since the capability existed before the stores refactor and disappeared without a replacement. If you'd rather treat it as a feature request I can move it to discussions.
Which project does this relate to?
Router
Describe the bug
Since the granular stores refactor there is no public reactive way to read
pendingMatches. It used to be part ofRouterState, so you could douseRouterState({ select: (s) => s.pendingMatches })(older issues like #925 quotethis.state.pendingMatchesdirectly). NowRouterStateno longer carriesmatches/pendingMatches, anduseMatchesonly selects over the committed matches. The data still exists inrouter.stores.pendingMatches, but no hook exposes it, so this looks like API surface that fell off in the refactor rather than a deliberate removal.Why I care: I'm migrating a fairly big app from react-router, and our tab strips light up the clicked tab the moment a navigation starts instead of when the target's chunk and loader have resolved. Everything needed for that is already in the router, since the pending matches are built synchronously at navigation start and carry the target route's
staticData(each route declares which tab it lights up there). I just can't get at them reactively.The workaround I ended up with works but is not pretty. It reaches into
router.storesand leans on event ordering (the pending stores happen to be populated beforeonBeforeLoadis emitted, and committed beforeonResolved):I'd love either a
usePendingMatcheshook mirroringuseMatches(select + structural sharing), orpendingMatchesback on the state thatuseRouterStateselects over. Happy to take a stab at a PR if you can point me at the preferred shape.Complete minimal reproducer
Not really applicable (missing API rather than wrong behavior), but here is the gap in one snippet:
Steps to Reproduce the Bug
useRouterState({ select: (s) => s.pendingMatches })no longer type-checks (and the value is not on the state object)useMatchesonly covers committed matchesrouter.stores.pendingMatchesExpected behavior
Some supported reactive way to read the pending matches, like
usePendingMatches({ select })orpendingMatchesonRouterStateagain.Platform
Additional context
Filing this as a bug rather than a discussion since the capability existed before the stores refactor and disappeared without a replacement. If you'd rather treat it as a feature request I can move it to discussions.