Skip to content

refactor: switch router stores to atom get/set API#7150

Merged
Sheraff merged 5 commits intomainfrom
refactor/router-store-atoms
Apr 11, 2026
Merged

refactor: switch router stores to atom get/set API#7150
Sheraff merged 5 commits intomainfrom
refactor/router-store-atoms

Conversation

@Sheraff
Copy link
Copy Markdown
Contributor

@Sheraff Sheraff commented Apr 11, 2026

Summary

  • switch the router store contract from state/setState to get/set and move the React and Vue adapters from createStore to createAtom
  • update the Solid adapter and replace router store reads and writes across router, start, devtools, and plugin codepaths without keeping a compatibility layer
  • refresh affected tests and snapshots to cover the new store API

Testing

  • CI=1 NX_DAEMON=false pnpm nx affected --target=test:eslint --exclude=examples/**,e2e/** --outputStyle=stream --skipRemoteCache
  • CI=1 NX_DAEMON=false pnpm nx affected --target=test:types --outputStyle=stream --skipRemoteCache
  • CI=1 NX_DAEMON=false pnpm nx affected --target=test:unit --outputStyle=stream --skipRemoteCache

Summary by CodeRabbit

  • Refactor
    • Unified internal store access and update behavior across router packages (React, Solid, Vue), core, devtools, HMR, SSR/hydration, and tests—improving consistency and reliability for server rendering, hydration, hot-reload, and runtime flows while preserving public APIs and external behavior.

Align the router store contract with createAtom so the React, Vue, and Solid adapters share one get/set shape without preserving the old state/setState compatibility layer.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65fb0cf4-2a28-494c-a4ec-ca90861dbdd2

📥 Commits

Reviewing files that changed from the base of the PR and between cd8381d and aed5dd4.

📒 Files selected for processing (1)
  • .changeset/yummy-eagles-work.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/yummy-eagles-work.md

📝 Walkthrough

Walkthrough

This PR replaces direct store .state/.setState() access with .get()/.set() across packages and swaps client-side reactive store creation from createStore to createAtom, updating framework integrations, SSR paths, plugins, devtools, tests, and snapshots.

Changes

Cohort / File(s) Summary
Core Store API
packages/router-core/src/stores.ts
Reworked store interfaces/implementations: stateget(), setState(updater) → `set(value
Router Core Logic
packages/router-core/src/router.ts, packages/router-core/src/load-matches.ts, packages/router-core/src/hash-scroll.ts, packages/router-core/src/scroll-restoration.ts, packages/router-core/src/ssr/createRequestHandler.ts, packages/router-core/src/ssr/ssr-client.ts, packages/router-core/src/ssr/ssr-server.ts
All reads/writes converted to .get()/.set(); store-driven control flows (matching, load lifecycle, SSR hydration/dehydration, status transitions) updated accordingly.
Store Factories (React/Solid/Vue & Tests)
packages/react-router/src/routerStores.ts, packages/solid-router/src/routerStores.ts, packages/vue-router/src/routerStores.ts, packages/router-core/tests/routerTestUtils.ts
Client factories now use createAtom instead of createStore; returned reactive store shape uses { get, set }; test utils updated to match.
Framework Integrations (React/Solid/Vue)
packages/*-router/src/... (Match, Matches, Transitioner, Scripts, headContentUtils, link, not-found, useLocation, useMatch, useRouterState, useCanGoBack, ssr/, start/)`
Replaced .state/.setState with .get()/.set() across components, hooks, transitioners, SSR render/hydration, script/head utilities, and redirect handling; logic preserved, accessors updated.
Start Client/Server & Hydration Guards
packages/start-client-core/src/client/hydrateStart.ts, packages/start-server-core/src/createStartHandler.ts, framework ssr/RouterClient.tsx files
Hydration gating and response header/status construction now read IDs/snapshots/statusCode via .get() instead of .state.
Router Plugin / HMR
packages/router-plugin/src/core/route-hmr-statement.ts, packages/router-plugin/tests/add-hmr/snapshots/*
HMR logic updated to read snapshot arrays via .get() and update per-match stores with .set() (updater callbacks retained); many snapshots adjusted accordingly.
Devtools
packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx
Devtools sync now reads pending/cached matches via .get() for both subscribe and signal fallbacks.
Tests & Snapshots
packages/router-core/tests/*, packages/router-plugin/tests/*, e2e/...
Test assertions and snapshots updated to use .get(); some tests switched test utils to createAtom.
Misc (redirects, server-fns, plugin tests)
various useServerFn, ssr client/server, router-plugin tests
Redirect _fromLocation and related flows now use .get() for location/status; other minor call-site adaptations to new store API.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • refactor: signal based reactivity #6704: Implements the same store API migration (replace .state/.setState() with .get()/.set() and switch createStorecreateAtom), touching overlapping store factory and consumer code.

Poem

🐰 I hopped from .state to .get() in tune,

Switched to .set() beneath the moon,
Atoms now sparkle, tests hum along,
Snapshots marching to a brand new song,
A rabbit cheers — the stores sing soon. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.64% 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 'refactor: switch router stores to atom get/set API' directly and clearly describes the main change: migrating the router store contract from state/setState to an atom-style get/set API.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/router-store-atoms

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

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud bot commented Apr 11, 2026

View your CI Pipeline Execution ↗ for commit aed5dd4

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

☁️ Nx Cloud last updated this comment at 2026-04-11 10:19:15 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 11, 2026

🚀 Changeset Version Preview

12 package(s) bumped directly, 20 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/react-router 1.168.13 → 1.168.14 Changeset
@tanstack/react-start 1.167.25 → 1.167.26 Changeset
@tanstack/router-core 1.168.9 → 1.168.10 Changeset
@tanstack/router-devtools-core 1.167.1 → 1.167.2 Changeset
@tanstack/router-plugin 1.167.12 → 1.167.13 Changeset
@tanstack/router-ssr-query-core 1.167.0 → 1.167.1 Changeset
@tanstack/solid-router 1.168.12 → 1.168.13 Changeset
@tanstack/solid-start 1.167.23 → 1.167.24 Changeset
@tanstack/start-client-core 1.167.11 → 1.167.12 Changeset
@tanstack/start-server-core 1.167.13 → 1.167.14 Changeset
@tanstack/vue-router 1.168.12 → 1.168.13 Changeset
@tanstack/vue-start 1.167.23 → 1.167.24 Changeset
@tanstack/react-router-devtools 1.166.11 → 1.166.12 Dependent
@tanstack/react-router-ssr-query 1.166.10 → 1.166.11 Dependent
@tanstack/react-start-client 1.166.30 → 1.166.31 Dependent
@tanstack/react-start-rsc 0.0.5 → 0.0.6 Dependent
@tanstack/react-start-server 1.166.31 → 1.166.32 Dependent
@tanstack/router-cli 1.166.25 → 1.166.26 Dependent
@tanstack/router-devtools 1.166.11 → 1.166.12 Dependent
@tanstack/router-generator 1.166.24 → 1.166.25 Dependent
@tanstack/router-vite-plugin 1.166.27 → 1.166.28 Dependent
@tanstack/solid-router-devtools 1.166.11 → 1.166.12 Dependent
@tanstack/solid-router-ssr-query 1.166.10 → 1.166.11 Dependent
@tanstack/solid-start-client 1.166.28 → 1.166.29 Dependent
@tanstack/solid-start-server 1.166.29 → 1.166.30 Dependent
@tanstack/start-plugin-core 1.167.22 → 1.167.23 Dependent
@tanstack/start-static-server-functions 1.166.27 → 1.166.28 Dependent
@tanstack/start-storage-context 1.166.23 → 1.166.24 Dependent
@tanstack/vue-router-devtools 1.166.11 → 1.166.12 Dependent
@tanstack/vue-router-ssr-query 1.166.10 → 1.166.11 Dependent
@tanstack/vue-start-client 1.166.28 → 1.166.29 Dependent
@tanstack/vue-start-server 1.166.29 → 1.166.30 Dependent

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 11, 2026

Bundle Size Benchmarks

  • Commit: d11977f16b91
  • Measured at: 2026-04-11T10:18:42.527Z
  • Baseline source: history:e61c49ce31af
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.41 KiB -102 B (-0.11%) 275.16 KiB 75.92 KiB ▅▅▅▆▆▇██▇▇▇▁
react-router.full 90.69 KiB -84 B (-0.09%) 286.35 KiB 78.79 KiB ▇▇▇██▇██▇▇▇▁
solid-router.minimal 35.57 KiB -33 B (-0.09%) 107.16 KiB 32.00 KiB ▁▁▁▅▅▆█████▆
solid-router.full 40.04 KiB -31 B (-0.08%) 120.70 KiB 35.95 KiB ▁▁▁▄▄▆█████▆
vue-router.minimal 53.37 KiB -100 B (-0.18%) 152.59 KiB 48.00 KiB ▁▁▁▂▂▆█████▁
vue-router.full 58.26 KiB -93 B (-0.16%) 168.05 KiB 52.17 KiB ▁▁▁▂▂▆█████▃
react-start.minimal 101.81 KiB -89 B (-0.09%) 323.00 KiB 88.02 KiB ▇▇▇██▇██▇▄▄▁
react-start.full 105.25 KiB -98 B (-0.09%) 333.35 KiB 91.08 KiB ▆▆▆▇▇▇██▇▅▅▁
solid-start.minimal 49.59 KiB -29 B (-0.06%) 153.01 KiB 43.70 KiB ▂▂▃▅▅▆███▃▃▁
solid-start.full 55.11 KiB -41 B (-0.07%) 169.24 KiB 48.49 KiB ▁▁▂▅▅▆███▄▄▂

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 11, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7150

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7150

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7150

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7150

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7150

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7150

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7150

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7150

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7150

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7150

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7150

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7150

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7150

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7150

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7150

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7150

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7150

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7150

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7150

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7150

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7150

commit: aed5dd4

Copy link
Copy Markdown
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/router-plugin/src/core/route-hmr-statement.ts (1)

19-37: ⚠️ Potential issue | 🟠 Major

Type definitions still reference setState but implementation uses set.

The AnyRouterWithPrivateMaps type declares setState on the match store maps (lines 23, 29, 35), but the implementation at line 125 calls store.set(...). This mismatch will cause TypeScript errors. Update the type to use set to match the RouterWritableStore interface from packages/router-core/src/stores.ts.

🔧 Proposed fix
 type AnyRouterWithPrivateMaps = AnyRouter & {
   routesById: Record<string, AnyRoute>
   routesByPath: Record<string, AnyRoute>
   stores: AnyRouter['stores'] & {
     cachedMatchStoresById: Map<
       string,
       {
-        setState: (updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void
+        set: (updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void
       }
     >
     pendingMatchStoresById: Map<
       string,
       {
-        setState: (updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void
+        set: (updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void
       }
     >
     activeMatchStoresById: Map<
       string,
       {
-        setState: (updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void
+        set: (updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void
       }
     >
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/core/route-hmr-statement.ts` around lines 19 - 37,
The type AnyRouterWithPrivateMaps declares match store maps with a setState
method but the implementation calls store.set(...); update the three map value
types (cachedMatchStoresById, pendingMatchStoresById, activeMatchStoresById) to
use set instead of setState so they match the RouterWritableStore interface from
packages/router-core/src/stores.ts; specifically replace the setState signature
((updater: (prev: AnyRouteMatch) => AnyRouteMatch) => void) with a set signature
((value: AnyRouteMatch | ((prev: AnyRouteMatch) => AnyRouteMatch)) => void) to
align types with the actual store API used by the code.
🧹 Nitpick comments (2)
packages/react-router/src/Scripts.tsx (1)

61-64: Read active matches once in the SSR branch.

Calling .get() twice is unnecessary and can be avoided by reusing a single snapshot value.

♻️ Proposed refactor
   if (isServer ?? router.isServer) {
-    const assetScripts = getAssetScripts(
-      router.stores.activeMatchesSnapshot.get(),
-    )
-    const scripts = getScripts(router.stores.activeMatchesSnapshot.get())
+    const activeMatches = router.stores.activeMatchesSnapshot.get()
+    const assetScripts = getAssetScripts(activeMatches)
+    const scripts = getScripts(activeMatches)
     return renderScripts(router, scripts, assetScripts)
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-router/src/Scripts.tsx` around lines 61 - 64, The code calls
router.stores.activeMatchesSnapshot.get() twice to compute assetScripts and
scripts; capture the snapshot once into a local variable (e.g. const
activeMatches = router.stores.activeMatchesSnapshot.get()) and pass that single
value into getAssetScripts(...) and getScripts(...) to avoid redundant snapshot
reads; change references in this block from direct .get() calls to the new
activeMatches variable (affecting getAssetScripts and getScripts usages).
packages/router-core/src/ssr/ssr-client.ts (1)

295-302: Redundant nested batch() call.

The inner router.batch() on line 296 is already inside the outer router.batch() from line 291. While this is not functionally incorrect (batch calls are typically idempotent), it adds unnecessary nesting.

♻️ Suggested simplification
     loadPromise.then(() => {
       router.batch(() => {
         // ensure router is not in status 'pending' anymore
         // this usually happens in Transitioner but if loading synchronously resolves,
         // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false
         if (router.stores.status.get() === 'pending') {
-          router.batch(() => {
-            router.stores.status.set(() => 'idle')
-            router.stores.resolvedLocation.set(() =>
-              router.stores.location.get(),
-            )
-          })
+          router.stores.status.set(() => 'idle')
+          router.stores.resolvedLocation.set(() =>
+            router.stores.location.get(),
+          )
         }
         // hide the pending component once the load is finished
         router.updateMatch(match.id, (prev) => ({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-core/src/ssr/ssr-client.ts` around lines 295 - 302, The
nested router.batch() call is redundant; remove the inner router.batch wrapper
and directly call router.stores.status.set and
router.stores.resolvedLocation.set inside the existing outer router.batch so you
only have a single router.batch scope updating router.stores.status and
router.stores.resolvedLocation (references: router.batch,
router.stores.status.set, router.stores.resolvedLocation.set,
router.stores.location.get).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/router-plugin/src/core/route-hmr-statement.ts`:
- Around line 19-37: The type AnyRouterWithPrivateMaps declares match store maps
with a setState method but the implementation calls store.set(...); update the
three map value types (cachedMatchStoresById, pendingMatchStoresById,
activeMatchStoresById) to use set instead of setState so they match the
RouterWritableStore interface from packages/router-core/src/stores.ts;
specifically replace the setState signature ((updater: (prev: AnyRouteMatch) =>
AnyRouteMatch) => void) with a set signature ((value: AnyRouteMatch | ((prev:
AnyRouteMatch) => AnyRouteMatch)) => void) to align types with the actual store
API used by the code.

---

Nitpick comments:
In `@packages/react-router/src/Scripts.tsx`:
- Around line 61-64: The code calls router.stores.activeMatchesSnapshot.get()
twice to compute assetScripts and scripts; capture the snapshot once into a
local variable (e.g. const activeMatches =
router.stores.activeMatchesSnapshot.get()) and pass that single value into
getAssetScripts(...) and getScripts(...) to avoid redundant snapshot reads;
change references in this block from direct .get() calls to the new
activeMatches variable (affecting getAssetScripts and getScripts usages).

In `@packages/router-core/src/ssr/ssr-client.ts`:
- Around line 295-302: The nested router.batch() call is redundant; remove the
inner router.batch wrapper and directly call router.stores.status.set and
router.stores.resolvedLocation.set inside the existing outer router.batch so you
only have a single router.batch scope updating router.stores.status and
router.stores.resolvedLocation (references: router.batch,
router.stores.status.set, router.stores.resolvedLocation.set,
router.stores.location.get).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c373cc76-99ae-4b9c-9bf7-b15b89f3028d

📥 Commits

Reviewing files that changed from the base of the PR and between d11977f and 616d4c9.

📒 Files selected for processing (67)
  • packages/react-router/src/Match.tsx
  • packages/react-router/src/Matches.tsx
  • packages/react-router/src/Scripts.tsx
  • packages/react-router/src/Transitioner.tsx
  • packages/react-router/src/headContentUtils.tsx
  • packages/react-router/src/link.tsx
  • packages/react-router/src/not-found.tsx
  • packages/react-router/src/routerStores.ts
  • packages/react-router/src/ssr/RouterClient.tsx
  • packages/react-router/src/ssr/renderRouterToStream.tsx
  • packages/react-router/src/ssr/renderRouterToString.tsx
  • packages/react-router/src/useCanGoBack.ts
  • packages/react-router/src/useLocation.tsx
  • packages/react-router/src/useMatch.tsx
  • packages/react-router/src/useRouterState.tsx
  • packages/react-start/src/useServerFn.ts
  • packages/router-core/src/hash-scroll.ts
  • packages/router-core/src/load-matches.ts
  • packages/router-core/src/router.ts
  • packages/router-core/src/scroll-restoration.ts
  • packages/router-core/src/ssr/createRequestHandler.ts
  • packages/router-core/src/ssr/ssr-client.ts
  • packages/router-core/src/ssr/ssr-server.ts
  • packages/router-core/src/stores.ts
  • packages/router-core/tests/callbacks.test.ts
  • packages/router-core/tests/granular-stores.test.ts
  • packages/router-core/tests/load.test.ts
  • packages/router-core/tests/routerTestUtils.ts
  • packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx
  • packages/router-plugin/src/core/route-hmr-statement.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@webpack-hot.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx
  • packages/router-ssr-query-core/src/index.ts
  • packages/solid-router/src/Match.tsx
  • packages/solid-router/src/Matches.tsx
  • packages/solid-router/src/Scripts.tsx
  • packages/solid-router/src/Transitioner.tsx
  • packages/solid-router/src/headContentUtils.tsx
  • packages/solid-router/src/link.tsx
  • packages/solid-router/src/not-found.tsx
  • packages/solid-router/src/routerStores.ts
  • packages/solid-router/src/ssr/RouterClient.tsx
  • packages/solid-router/src/ssr/renderRouterToStream.tsx
  • packages/solid-router/src/ssr/renderRouterToString.tsx
  • packages/solid-router/src/useCanGoBack.ts
  • packages/solid-router/src/useLocation.tsx
  • packages/solid-router/src/useMatch.tsx
  • packages/solid-router/src/useRouterState.tsx
  • packages/solid-start/src/useServerFn.ts
  • packages/start-client-core/src/client/hydrateStart.ts
  • packages/start-server-core/src/createStartHandler.ts
  • packages/vue-router/src/Match.tsx
  • packages/vue-router/src/Transitioner.tsx
  • packages/vue-router/src/link.tsx
  • packages/vue-router/src/routerStores.ts
  • packages/vue-router/src/ssr/RouterClient.tsx
  • packages/vue-router/src/ssr/renderRouterToStream.tsx
  • packages/vue-router/src/ssr/renderRouterToString.tsx
  • packages/vue-router/src/useMatch.tsx
  • packages/vue-router/src/useRouterState.tsx
  • packages/vue-start/src/useServerFn.ts

Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud bot left a comment

Choose a reason for hiding this comment

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

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We update waitForRouteRemovalReload in the HMR e2e suite to read the active matches snapshot via .get() instead of .state, aligning the test with the atom get/set API introduced by this refactor. Without this change, .state resolves to undefined, the waitForFunction predicate never returns true, and both affected tests time out after 30 seconds.

Warning

We could not verify this fix.

diff --git a/e2e/react-start/hmr/tests/app.spec.ts b/e2e/react-start/hmr/tests/app.spec.ts
index 8166f94610..f9e57e05c9 100644
--- a/e2e/react-start/hmr/tests/app.spec.ts
+++ b/e2e/react-start/hmr/tests/app.spec.ts
@@ -142,9 +142,9 @@ async function waitForRouteLoaderCrumb(
 async function waitForRouteRemovalReload(page: Page) {
   await page.waitForFunction(() => {
     const router = (window as any).__TSR_ROUTER__
-    const match = router?.stores?.activeMatchesSnapshot?.state?.find(
-      (entry: any) => entry.routeId === '/child',
-    )
+    const match = router?.stores?.activeMatchesSnapshot
+      ?.get()
+      ?.find((entry: any) => entry.routeId === '/child')
 
     return !match?.invalid && match?.isFetching === false
   })

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally kzDs-0wvr

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 11, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing refactor/router-store-atoms (aed5dd4) with main (e61c49c)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (d11977f) during the generation of this report, so e61c49c was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Sheraff added 2 commits April 11, 2026 11:38
Match the atom setter contract by accepting direct values in RouterWritableStore and replace callback setters that do not depend on the previous value.
Update the HMR helper types and tests for the atom get/set API, reuse the SSR active match snapshot once, and remove the redundant nested batch during hydration cleanup.
Copy link
Copy Markdown
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/router-core/src/ssr/ssr-client.ts (1)

173-202: ⚠️ Potential issue | 🟠 Major

Pass activeMatches into contextFnContext.matches.

activeMatches is fetched from the live match stores after router.options.hydrate?.(...), but Line 201 still passes the earlier matches array into route.options.context. Since activeMatchesSnapshot.get() rebuilds from the current store pool (packages/router-core/src/stores.ts:143-145, 292-300), those arrays can diverge if the hydrate hook updates match state. That leaves context() reconstructing against stale matches while the rest of this block uses activeMatches.

Suggested fix
         if (route.options.context) {
           const contextFnContext: RouteContextOptions<any, any, any, any, any> =
             {
               deps: match.loaderDeps,
               params: match.params,
               context: parentContext ?? {},
               location,
               navigate: (opts: any) =>
                 router.navigate({
                   ...opts,
                   _fromLocation: location,
                 }),
               buildLocation: router.buildLocation,
               cause: match.cause,
               abortController: match.abortController,
               preload: false,
-              matches,
+              matches: activeMatches,
               routeId: route.id,
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-core/src/ssr/ssr-client.ts` around lines 173 - 202, The
context passed to route.options.context is using the stale matches array; update
the construction of contextFnContext inside the loop (where matches is assigned
to the matches field) to use activeMatches instead (i.e., set
contextFnContext.matches = activeMatches) so context() runs against the current
activeMatchesSnapshot.get() data after router.options.hydrate has run; adjust
any references inside the async map for match so the matches property
consistently points to activeMatches rather than the earlier matches variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/router-core/src/ssr/ssr-client.ts`:
- Around line 173-202: The context passed to route.options.context is using the
stale matches array; update the construction of contextFnContext inside the loop
(where matches is assigned to the matches field) to use activeMatches instead
(i.e., set contextFnContext.matches = activeMatches) so context() runs against
the current activeMatchesSnapshot.get() data after router.options.hydrate has
run; adjust any references inside the async map for match so the matches
property consistently points to activeMatches rather than the earlier matches
variable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2bedb064-3fc0-4df6-b0c4-b75b918bb950

📥 Commits

Reviewing files that changed from the base of the PR and between f1441b9 and cd8381d.

📒 Files selected for processing (4)
  • e2e/react-start/hmr/tests/app.spec.ts
  • packages/react-router/src/Scripts.tsx
  • packages/router-core/src/ssr/ssr-client.ts
  • packages/router-plugin/src/core/route-hmr-statement.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/react-router/src/Scripts.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/router-plugin/src/core/route-hmr-statement.ts

@Sheraff Sheraff merged commit 459057c into main Apr 11, 2026
17 checks passed
@Sheraff Sheraff deleted the refactor/router-store-atoms branch April 11, 2026 10:42
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.

1 participant