Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Jan 31, 2026

This PR proposes that we don't expose the pendingMatches. These are updated many times during the loading process, when in reality what we want is "nothing while it loads, and then mount the routes". Each store update triggers every router hook on the page every time, and some navigations can cause many store updates because of the presence of pendingMatches in the store.

SSR benchmark (not the main target of this PR)

before

[1] 92k requests in 30.01s, 817 MB read
[1] 
[1] === SSR Benchmark Results ===
[1] Total requests: 71923
[1] Requests/sec: 2398
[1] Latency (avg): 2.75ms
[1] Latency (p99): 8ms
[1] Throughput: 25.98 MB/s

after

[1] 97k requests in 30.01s, 870 MB read
[1] 
[1] === SSR Benchmark Results ===
[1] Total requests: 76597
[1] Requests/sec: 2553.77
[1] Latency (avg): 2.66ms
[1] Latency (p99): 8ms
[1] Throughput: 27.67 MB/s

Summary by CodeRabbit

  • Breaking Changes

    • Removed the public pendingMatches field from RouterState — rely on remaining router state fields and public helpers.
  • Documentation

    • Removed guidance and examples referencing pendingMatches across framework docs and hooks.
  • New Features

    • Added optional route configuration to supply a custom notFound component.
  • Devtools

    • State views now reflect matches and cachedMatches only; pendingMatches is no longer shown.
  • Tests

    • Updated assertions and expected update counts to align with public observation patterns.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

Public RouterState.pendingMatches was removed and pending-match tracking was internalized in router-core using private maps, indices, queued match update maps, and a microtask flush. Documentation, framework adapters, HMR, devtools, SSR hydration, and tests were updated to stop referencing public pendingMatches.

Changes

Cohort / File(s) Summary
Documentation
docs/router/framework/react/api/router/RouterStateType.md, docs/router/framework/react/api/router/useChildMatchesHook.md, docs/router/framework/react/api/router/useParentMatchesHook.md
Removed pendingMatches from RouterState docs and removed guidance to use router.state.pendingMatches.
Router Core
packages/router-core/src/router.ts
Removed public RouterState.pendingMatches; added private fields (__pendingMatches, __matchesById, indices, queued update maps, flush flag) and helper methods (rebuild indices, queueMatchUpdate, updateMatchInternal). Rewrote load/beforeLoad/cancel/commit/update flows to use internal pending tracking and O(1) lookups.
Router Core — Load/Helpers
packages/router-core/src/load-matches.ts
Switched from router.getMatch() to direct per-match access via local matches array and matchesIndex; added localUpdateMatch to keep local array and router indices in sync.
Router Core — SSR
packages/router-core/src/ssr/ssr-client.ts
After hydrating matches, call to rebuildMatchesById() added to sync internal lookup maps before running hydrate hook.
Core Tests / API
packages/router-core/tests/load.test.ts
Replaced assertions that inspected pendingMatches with status checks and router.getMatch() lookups; added notFoundComponent to some test routes.
DevTools
packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx
Stopped referencing pendingMatches; active-match composition and memoization now rely only on matches and cachedMatches.
HMR / Plugin
packages/router-plugin/src/core/route-hmr-statement.ts, packages/router-plugin/tests/add-hmr/snapshots/...
Removed checks against router.state.pendingMatches from HMR invalidation logic; invalidation now only considers router.state.matches.
Framework — Solid
packages/solid-router/src/useMatch.tsx, packages/solid-router/tests/*
Removed pendingMatches-based lookup and related test assertions; simplified shouldThrow logic to depend on isLoading, isTransitioning, and shouldThrow.
Framework — Vue
packages/vue-router/src/useMatch.tsx, packages/vue-router/tests/*
Selector now returns { match, shouldThrowError }; throw logic moved to a computed guard. Removed pendingMatches checks and adjusted tests.
Tests — Update Counts / Flakiness
packages/*/tests/store-updates-during-navigation.test.tsx (React, Solid, Vue)
Adjusted expected store-update counts and flakiness thresholds to reflect changed internal update patterns after internalizing pending matches.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller as Caller (load / beforeLoad / update)
  participant Router as RouterCore
  participant Queue as __queuedMatchUpdates
  participant Flush as MicrotaskFlush
  participant Store as State Commit / Subscribers

  Caller->>Router: request match update (updateMatch / load)
  Router->>Queue: queueMatchUpdate(id, match)
  Note right of Queue: updates buffered in maps (__queuedMatchUpdates / __queuedCachedMatchUpdates)
  Router->>Flush: schedule microtask flush (if not already scheduled)
  Flush->>Router: run flushQueuedMatchUpdates()
  Router->>Router: updateMatchInternal -> apply to __matchesById / __pendingMatches
  Router->>Router: rebuildIndices / rebuildMatchesById
  Router->>Store: commit new matches/state
  Store->>Caller: notify subscribers, devtools, plugins
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰
I hopped through code at break of day,
Tucked pending matches in burrows away,
Indexed, queued, and snugly stored,
Public fields gone — my patch explored,
Carrots, commits, and cozy bytes 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: removing pendingMatches from public store and reserving store updates for reactivity, which aligns with all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-router-core-only-update-store-for-public-updates

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Jan 31, 2026

View your CI Pipeline Execution ↗ for commit a288124

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 10m 43s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 45s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-01 12:17:08 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 31, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6563

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6563

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6563

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6563

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6563

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6563

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6563

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6563

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6563

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6563

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6563

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6563

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6563

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6563

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6563

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6563

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6563

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6563

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6563

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6563

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6563

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6563

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6563

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6563

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6563

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6563

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6563

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6563

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6563

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6563

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6563

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6563

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6563

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6563

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6563

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6563

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6563

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6563

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6563

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6563

commit: a288124

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/vue-router/src/useMatch.tsx`:
- Around line 75-76: The select callback in useMatch's selector uses explicit
any for state and matches; fix by calling useRouterState with the TRouter
generic (useRouterState<TRouter>()) so the selector infers
RouterState<TRouter['routeTree']>, and update the select signature to use that
inferred type (or explicitly type state as RouterState<TRouter['routeTree']>) so
state.matches and the match variable are properly typed; reference the
useRouterState call and the select function that accesses state.matches (mirror
the pattern used in Matches.tsx where RouterState<TRouter['routeTree']> is
used).

Comment on lines 75 to 76
select: (state: any) => {
const match = state.matches.find((d: any) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the file to see the context around lines 75-76
fd -type f -name 'useMatch.tsx' packages/vue-router/src/ | head -1 | xargs wc -l

Repository: TanStack/router

Length of output: 248


🏁 Script executed:

# Read the specific section of the file
cat -n packages/vue-router/src/useMatch.tsx | sed -n '70,85p'

Repository: TanStack/router

Length of output: 748


🏁 Script executed:

# Check if useRouterState is defined and what types it exposes
fd -type f -name '*.tsx' -o -name '*.ts' packages/vue-router/src/ | xargs rg -l "useRouterState" | head -5

Repository: TanStack/router

Length of output: 524


🏁 Script executed:

# Find useRouterState definition in Vue router
fd -type f -name '*.tsx' -o -name '*.ts' packages/vue-router/src/ | xargs rg -l "export.*useRouterState" | head -3

Repository: TanStack/router

Length of output: 368


🏁 Script executed:

# Check the imports and type definitions at the top of useMatch.tsx
cat -n packages/vue-router/src/useMatch.tsx | head -40

Repository: TanStack/router

Length of output: 1370


🏁 Script executed:

# Look at useRouterState test file to understand expected typing
cat -n packages/vue-router/tests/useRouterState.test-d.tsx | head -80

Repository: TanStack/router

Length of output: 1947


🏁 Script executed:

# Read the useRouterState implementation
cat -n packages/vue-router/src/useRouterState.tsx

Repository: TanStack/router

Length of output: 2212


🏁 Script executed:

# Check the RouterState type definition
rg -n "export.*type RouterState" packages/vue-router/src/

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Read the full function signature of useMatch to understand available generics
cat -n packages/vue-router/src/useMatch.tsx | sed -n '50,85p'

Repository: TanStack/router

Length of output: 1427


🏁 Script executed:

# Also check if there are other usages of useRouterState in the codebase to see the pattern
rg -n "useRouterState\(" packages/vue-router/src/ -A2

Repository: TanStack/router

Length of output: 6007


🏁 Script executed:

# Check if removing `any` would actually type correctly without the generic parameter
# by looking at how TypeScript would infer it
rg -n "useRouterState<" packages/vue-router/src/ -B1 -A2

Repository: TanStack/router

Length of output: 346


🏁 Script executed:

# Verify if TRouter is indeed available in the useMatch function scope
cat -n packages/vue-router/src/useMatch.tsx | sed -n '54,75p'

Repository: TanStack/router

Length of output: 865


🏁 Script executed:

# Check one working example from Matches.tsx that explicitly types
cat -n packages/vue-router/src/Matches.tsx | sed -n '310,320p'

Repository: TanStack/router

Length of output: 514


Pass TRouter generic to useRouterState for proper type inference.

Line 75-76 uses explicit any types. The root cause is that useRouterState is called without the <TRouter> generic parameter. Either pass <TRouter> to useRouterState and rely on inference, or explicitly type the state parameter as RouterState<TRouter['routeTree']> to match the pattern in Matches.tsx:315.

♻️ Suggested change
-    select: (state: any) => {
-      const match = state.matches.find((d: any) =>
+    select: (state) => {
+      const match = state.matches.find((d) =>

Should be combined with passing the generic:

-  const matchState = useRouterState({
+  const matchState = useRouterState<TRouter>({
     select: (state) => {
🤖 Prompt for AI Agents
In `@packages/vue-router/src/useMatch.tsx` around lines 75 - 76, The select
callback in useMatch's selector uses explicit any for state and matches; fix by
calling useRouterState with the TRouter generic (useRouterState<TRouter>()) so
the selector infers RouterState<TRouter['routeTree']>, and update the select
signature to use that inferred type (or explicitly type state as
RouterState<TRouter['routeTree']>) so state.matches and the match variable are
properly typed; reference the useRouterState call and the select function that
accesses state.matches (mirror the pattern used in Matches.tsx where
RouterState<TRouter['routeTree']> is used).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants