Skip to content

fix(react-router): clear stale route errors on navigation#7136

Merged
schiller-manuel merged 2 commits intomainfrom
fix-7121
Apr 10, 2026
Merged

fix(react-router): clear stale route errors on navigation#7136
schiller-manuel merged 2 commits intomainfrom
fix-7121

Conversation

@schiller-manuel
Copy link
Copy Markdown
Contributor

@schiller-manuel schiller-manuel commented Apr 10, 2026

fixes #7121

Summary by CodeRabbit

  • Bug Fixes

    • Resolved an error boundary state issue where the error component from a failed route could briefly appear when navigating to a subsequent route.
  • Tests

    • Added comprehensive test coverage to verify error states no longer leak across route navigations, including errors thrown from loaders and component rendering.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

A bug fix for the CatchBoundary component in React Router that prevents stale error states from leaking to subsequent routes during navigation. The error reset mechanism was refactored from componentDidUpdate to getDerivedStateFromProps for more reliable cleanup. A comprehensive test was added to verify the fix.

Changes

Cohort / File(s) Summary
Release Documentation
.changeset/ten-cougars-jump.md
New changeset entry documenting a patch-level fix for error boundary state leakage issue in @tanstack/react-router.
Error Boundary Implementation
packages/react-router/src/CatchBoundary.tsx
Refactored error state reset from componentDidUpdate lifecycle to getDerivedStateFromProps. Now clears error when resetKey changes within the derived state method, eliminating stale error state propagation to subsequent routes.
Error Component Tests
packages/react-router/tests/errorComponent.test.tsx
Added comprehensive test suite stale route errors do not leak after navigation covering both loader and component render errors. Test verifies error does not persist when navigating away from failed routes. Updated test utilities to use act() wrapper for proper React timing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Errors used to cling and stay,
Following routes far away,
But now with hooks set just right,
Each navigation shines bright,
No ghosts of old errors astray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main fix: clearing stale route errors that occur after navigation, which directly addresses the core issue.
Linked Issues check ✅ Passed The PR changes fully address issue #7121 by fixing the stale error propagation: the modified CatchBoundary now clears errors when resetKey changes, preventing errors from one route leaking to subsequent routes.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing stale route errors: the Changeset documents the fix, CatchBoundary implements the error-clearing logic, and the test validates the fix for both loader and render errors.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-7121

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 10, 2026

View your CI Pipeline Execution ↗ for commit 15d2903

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

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

🚀 Changeset Version Preview

3 package(s) bumped directly, 9 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/react-router 1.168.11 → 1.168.12 Changeset
@tanstack/solid-router 1.168.10 → 1.168.11 Changeset
@tanstack/vue-router 1.168.10 → 1.168.11 Changeset
@tanstack/react-start 1.167.18 → 1.167.19 Dependent
@tanstack/react-start-client 1.166.27 → 1.166.28 Dependent
@tanstack/react-start-server 1.166.27 → 1.166.28 Dependent
@tanstack/solid-start 1.167.17 → 1.167.18 Dependent
@tanstack/solid-start-client 1.166.25 → 1.166.26 Dependent
@tanstack/solid-start-server 1.166.25 → 1.166.26 Dependent
@tanstack/vue-start 1.167.17 → 1.167.18 Dependent
@tanstack/vue-start-client 1.166.25 → 1.166.26 Dependent
@tanstack/vue-start-server 1.166.25 → 1.166.26 Dependent

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 10, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 15d2903

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-router/src/CatchBoundary.tsx`:
- Around line 41-48: getDerivedStateFromProps is using unsafe any types and the
state's resetKey is mis-typed as string; change the method signature to use
proper types (props: { getResetKey: () => string | number } and state: {
resetKey?: string | number; error: Error | null } or the existing
CatchBoundaryState type updated to make resetKey optional and accept
number|string), and update the state type declaration so resetKey is optional
(resetKey?: string | number) to match the async initialization pattern where
initial state may omit resetKey; ensure getDerivedStateFromProps returns {
resetKey } or { resetKey, error: null } with the correctly typed resetKey.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68b82fc8-adde-418f-ae5e-f79114770e4f

📥 Commits

Reviewing files that changed from the base of the PR and between f7e9c5e and c48d290.

📒 Files selected for processing (3)
  • .changeset/ten-cougars-jump.md
  • packages/react-router/src/CatchBoundary.tsx
  • packages/react-router/tests/errorComponent.test.tsx

Comment on lines +41 to +48
static getDerivedStateFromProps(props: any, state: any) {
const resetKey = props.getResetKey()

if (state.error && state.resetKey !== resetKey) {
return { resetKey, error: null }
}

return { resetKey }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the CatchBoundary.tsx file to see the current state
cd /repo && find . -name "CatchBoundary.tsx" -type f | head -5

Repository: TanStack/router

Length of output: 114


🏁 Script executed:

# Check the full CatchBoundary component, especially lines 41-48 and state definition
cat -n packages/react-router/src/CatchBoundary.tsx | head -100

Repository: TanStack/router

Length of output: 3439


🏁 Script executed:

# Now check Match.tsx to see how resetKey is derived from loadedAt at lines 74-99
cat -n packages/react-router/src/Match.tsx | sed -n '60,110p'

Repository: TanStack/router

Length of output: 1980


🏁 Script executed:

# Search for the type definition of loadedAt in the router store
rg "loadedAt" --type ts --type tsx -A 2 -B 2 | head -50

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

# Search for loadedAt type definition with proper TypeScript file type
rg "loadedAt" -A 2 -B 2 --type ts | head -80

Repository: TanStack/router

Length of output: 4975


🏁 Script executed:

# Also check the type signature of the store to see what loadedAt should be
rg "loadedAt" --type ts --glob "*.ts" -A 1 -B 1 | grep -E "(loadedAt|:.*number|:.*string)" | head -20

Repository: TanStack/router

Length of output: 552


Type the reset-key path instead of erasing it with any.

The getDerivedStateFromProps method on line 41 uses any types, bypassing compiler checks. Additionally, resetKey is declared as string in the state type (line 39), but the prop signature and actual usage show it should be number | string (as returned by getResetKey and sourced from router.stores.loadedAt, which is typed as number). Since the initial state { error: null } does not include resetKey, it should be optional in the type to reflect the async initialization pattern in class components.

♻️ Suggested typing fix
-  state = { error: null } as { error: Error | null; resetKey: string }
+  state = { error: null } as {
+    error: Error | null
+    resetKey?: number | string
+  }

-  static getDerivedStateFromProps(props: any, state: any) {
+  static getDerivedStateFromProps(
+    props: { getResetKey: () => number | string },
+    state: { error: Error | null; resetKey?: number | string },
+  ) {
     const resetKey = props.getResetKey()

     if (state.error && state.resetKey !== resetKey) {
       return { resetKey, error: null }
     }

     return { resetKey }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-router/src/CatchBoundary.tsx` around lines 41 - 48,
getDerivedStateFromProps is using unsafe any types and the state's resetKey is
mis-typed as string; change the method signature to use proper types (props: {
getResetKey: () => string | number } and state: { resetKey?: string | number;
error: Error | null } or the existing CatchBoundaryState type updated to make
resetKey optional and accept number|string), and update the state type
declaration so resetKey is optional (resetKey?: string | number) to match the
async initialization pattern where initial state may omit resetKey; ensure
getDerivedStateFromProps returns { resetKey } or { resetKey, error: null } with
the correctly typed resetKey.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

Bundle Size Benchmarks

  • Commit: b29d64de0c40
  • Measured at: 2026-04-10T19:34:16.340Z
  • Baseline source: history:f7e9c5e32379
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.51 KiB +22 B (+0.02%) 275.69 KiB 76.08 KiB █▁▁▁▁▁▂▃▃▅▇▅
react-router.full 90.77 KiB -7 B (-0.01%) 286.88 KiB 78.95 KiB █▁▁▁▁▁▂▂▂▂▃▂
solid-router.minimal 35.60 KiB +43 B (+0.12%) 107.36 KiB 31.94 KiB ▅▁▁▁▁▁▁▅▅▆█
solid-router.full 40.07 KiB +45 B (+0.11%) 120.90 KiB 35.96 KiB ▆▁▁▁▁▁▁▅▅▆█
vue-router.minimal 53.46 KiB +82 B (+0.15%) 153.16 KiB 48.00 KiB ▅▁▁▁▁▁▂▃▃▆█
vue-router.full 58.36 KiB +111 B (+0.19%) 168.62 KiB 52.25 KiB ▄▁▁▁▁▁▁▂▂▆█
react-start.minimal 102.00 KiB -7 B (-0.01%) 323.94 KiB 88.16 KiB █▁▂▂▂▂▃▃▃▂▅▃
react-start.full 105.40 KiB +19 B (+0.02%) 334.28 KiB 91.03 KiB █▁▁▁▂▂▃▃▄▅▇▅
solid-start.minimal 49.70 KiB +41 B (+0.08%) 153.61 KiB 43.77 KiB ▅▁▁▁▁▁▂▅▅▆█
solid-start.full 55.21 KiB +42 B (+0.07%) 169.84 KiB 48.58 KiB ▅▁▁▁▁▁▂▅▆▆█

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

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 10, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing fix-7121 (15d2903) with main (b29d64d)

Open in CodSpeed

@schiller-manuel
Copy link
Copy Markdown
Contributor Author

@copilot

In @packages/react-router/src/CatchBoundary.tsx around lines 41 - 48,
getDerivedStateFromProps is using unsafe any types and the state's resetKey is
mis-typed as string; change the method signature to use proper types (props: {
getResetKey: () => string | number } and state: { resetKey?: string | number;
error: Error | null } or the existing CatchBoundaryState type updated to make
resetKey optional and accept number|string), and update the state type
declaration so resetKey is optional (resetKey?: string | number) to match the
async initialization pattern where initial state may omit resetKey; ensure
getDerivedStateFromProps returns { resetKey } or { resetKey, error: null } with
the correctly typed resetKey.

Agent-Logs-Url: https://github.com/TanStack/router/sessions/1367dbce-0eb5-4a4b-8fbf-995ce8479c3c

Co-authored-by: schiller-manuel <6340397+schiller-manuel@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

@copilot

...

Fixed in 15d2903. Updated getDerivedStateFromProps to use proper typed parameters instead of any, and changed the state's resetKey from string to string | number (optional) to match the props type and async initialization pattern.

@schiller-manuel schiller-manuel merged commit 70ec1da into main Apr 10, 2026
16 checks passed
@schiller-manuel schiller-manuel deleted the fix-7121 branch April 10, 2026 20:37
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.

errorComponent receives stale error from previous route after navigation

2 participants