Skip to content

fix(react-router): clear stale error in CatchBoundary on route change#7122

Open
ali-idrizi wants to merge 2 commits intoTanStack:mainfrom
ali-idrizi:fix/error-boundary-stale-error-on-navigation
Open

fix(react-router): clear stale error in CatchBoundary on route change#7122
ali-idrizi wants to merge 2 commits intoTanStack:mainfrom
ali-idrizi:fix/error-boundary-stale-error-on-navigation

Conversation

@ali-idrizi
Copy link
Copy Markdown
Contributor

@ali-idrizi ali-idrizi commented Apr 8, 2026

fixes #7121

I have modified the CatchBoundary to reset the error together with the resetKey in getDerivedStateFromProps. This removes the need for componentDidUpdate to call reset(), and makes the render resetKey mismatch check redundant.

An existing test that was testing the exact case in #7121 but for onError is slightly modified to also test no calls are made to errorComponent.

Summary by CodeRabbit

  • Bug Fixes

    • Refined error state management in error boundaries to better handle error clearing and reset when navigating between routes.
  • Tests

    • Improved error component callback testing to verify proper behavior during route navigation with errors.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

The changes refactor error reset handling in CatchBoundaryImpl by moving reset logic from componentDidUpdate to getDerivedStateFromProps, ensuring error state clears when the reset key changes. A corresponding test validates that error state does not leak between routes during navigation.

Changes

Cohort / File(s) Summary
Error Reset Refactoring
packages/react-router/src/CatchBoundary.tsx
Moved reset logic from componentDidUpdate lifecycle method to getDerivedStateFromProps. Now computes resetKey from props and clears stored errors when resetKey changes, eliminating the previous conditional render-time suppression of error rendering.
Error Boundary Test Validation
packages/react-router/tests/link.test.tsx
Updated test "when navigating away from a route with a loader that errors" to spy on indexErrorComponent and verify it is not called, confirming error state does not leak between routes after navigation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hops with glee through the derived state,
Stale errors reset, oh what fate!
Keys that change bring clarity bright,
Error boundaries now hold true and tight!

🚥 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 PR title clearly and concisely summarizes the main change: fixing the CatchBoundary to clear stale errors during route changes, which directly addresses the core issue.
Linked Issues check ✅ Passed The code changes directly address issue #7121 by moving error reset logic into getDerivedStateFromProps, preventing stale error leakage to subsequent routes' errorComponents.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the stale error issue: CatchBoundary refactoring and test adjustment to verify errorComponent is not called with stale 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 unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

View your CI Pipeline Execution ↗ for commit 669f802

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

☁️ Nx Cloud last updated this comment at 2026-04-08 14:41:19 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Bundle Size Benchmarks

  • Commit: 5a81726f0a2f
  • Measured at: 2026-04-08T14:33:13.881Z
  • Baseline source: history:796406da66cf
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.47 KiB -16 B (-0.02%) 275.69 KiB 76.10 KiB ████▁▁▁▁▁▂▃▁
react-router.full 90.77 KiB -8 B (-0.01%) 286.88 KiB 78.84 KiB ▆▆▆█▁▁▁▁▁▂▂▂
solid-router.minimal 35.56 KiB 0 B (0.00%) 107.26 KiB 31.94 KiB ████▁▁▁▁▁▂█
solid-router.full 40.03 KiB 0 B (0.00%) 120.79 KiB 35.94 KiB ████▁▁▁▁▁▂▆
vue-router.minimal 53.38 KiB 0 B (0.00%) 153.07 KiB 47.94 KiB ████▁▁▁▁▁▂▄
vue-router.full 58.25 KiB 0 B (0.00%) 168.53 KiB 52.18 KiB ████▁▁▁▁▁▂▄
react-start.minimal 101.99 KiB -18 B (-0.02%) 323.93 KiB 88.22 KiB ▅▅▅█▁▂▂▂▂▃▃▂
react-start.full 105.37 KiB -14 B (-0.01%) 334.28 KiB 91.17 KiB ▆▆▆█▁▁▁▂▂▃▃▂
solid-start.minimal 49.66 KiB 0 B (0.00%) 153.51 KiB 43.84 KiB ▇▇▇▇▁▁▁▂▂▂█
solid-start.full 55.17 KiB 0 B (0.00%) 169.74 KiB 48.46 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 8, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 669f802

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.

🧹 Nitpick comments (1)
packages/react-router/src/CatchBoundary.tsx (1)

40-46: Use proper types instead of any for type safety.

The getDerivedStateFromProps method uses any for both props and state parameters, which bypasses TypeScript's type checking. Consider using the component's actual prop and state types.

♻️ Proposed fix for type safety
-  static getDerivedStateFromProps(props: any, state: any) {
+  static getDerivedStateFromProps(
+    props: {
+      getResetKey: () => number | string
+      children: (props: { error: Error | null; reset: () => void }) => React.ReactNode
+      onCatch?: (error: Error, errorInfo: ErrorInfo) => void
+    },
+    state: { error: Error | null; resetKey: number | string },
+  ) {
     const resetKey = props.getResetKey()
     if (state.error && state.resetKey !== resetKey) {
       return { resetKey: resetKey, error: null }
     }
     return { resetKey: resetKey }
   }

As per coding guidelines: "Use TypeScript strict mode with extensive type safety".

🤖 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 40 - 46, Replace
the use of `any` in the static method signature with the component's concrete
prop and state types: change `getDerivedStateFromProps(props: any, state: any)`
to use the component's Props and State (e.g., `getDerivedStateFromProps(props:
CatchBoundaryProps, state: CatchBoundaryState)` or the generic types declared on
the `CatchBoundary` class), make the method return a correctly typed
Partial<State> or null, and ensure the State interface includes `error` and
`resetKey` (the method reads `state.error` and `state.resetKey`) while Props
exposes `getResetKey(): string` so `const resetKey = props.getResetKey()`
type-checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react-router/src/CatchBoundary.tsx`:
- Around line 40-46: Replace the use of `any` in the static method signature
with the component's concrete prop and state types: change
`getDerivedStateFromProps(props: any, state: any)` to use the component's Props
and State (e.g., `getDerivedStateFromProps(props: CatchBoundaryProps, state:
CatchBoundaryState)` or the generic types declared on the `CatchBoundary`
class), make the method return a correctly typed Partial<State> or null, and
ensure the State interface includes `error` and `resetKey` (the method reads
`state.error` and `state.resetKey`) while Props exposes `getResetKey(): string`
so `const resetKey = props.getResetKey()` type-checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: efbe2fab-6225-41b8-bfb0-c14a7b2b6814

📥 Commits

Reviewing files that changed from the base of the PR and between 5a81726 and 669f802.

📒 Files selected for processing (2)
  • packages/react-router/src/CatchBoundary.tsx
  • packages/react-router/tests/link.test.tsx

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 8, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing ali-idrizi:fix/error-boundary-stale-error-on-navigation (669f802) with main (796406d)1

Open in CodSpeed

Footnotes

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

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

1 participant