Skip to content

fix(react-router): render errorComponent for falsy thrown values - #8238

Closed
vladavoX wants to merge 1 commit into
TanStack:mainfrom
vladavoX:fix-catchboundary-falsy-errors
Closed

fix(react-router): render errorComponent for falsy thrown values#8238
vladavoX wants to merge 1 commit into
TanStack:mainfrom
vladavoX:fix-catchboundary-falsy-errors

Conversation

@vladavoX

@vladavoXvladavoX commented Sep 4, 2026

Copy link
Copy Markdown

🎯 Changes

Fixes#8123.

CatchBoundary decided whether it had caught an error from the truthiness of the thrown value. A falsy throw (throw undefined, null, '') was stored but the render check failed, so the boundary re-rendered the same crashing children and React escalated the error past it. Since every route boundary and the global boundary run the same check, the error skipped every errorComponent and unmounted the whole tree.

  • CatchBoundary state holds a boxed caught: { error } | null, so the presence of the box marks "caught" independently of the value. The getDerivedStateFromProps reset path and reset() clear the box.
  • The default ErrorComponent reads error?.message. With the boundary fixed it now receives undefined/null, and the previous error.message threw inside its own fallback — which escalated again and still blanked the page.
  • The global boundary's development warning uses String(error?.message || error) for the same reason. This keeps the old toString() fallback, so new Error('') and error-shaped objects still print an identity.
  • Tests in errorComponent.test.tsx cover undefined, null and '' thrown from a route component, from a loader, and with no errorComponent configured. All nine fail on main.

The same truthiness check exists in lazyRouteComponent (an import rejected with a falsy reason retries forever instead of throwing) and in vue-router's CatchBoundary. Both are left out to keep this PR to the reported issue; happy to send follow-ups.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with the relevant test commands, or tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes
    • Route error components now render correctly when routes throw falsy values such as null, undefined, or an empty string.
    • Applications no longer unexpectedly unmount or escalate these errors to the global boundary.
    • Error messages and development warnings now handle missing or falsy error values safely.
  • Tests
    • Added coverage for falsy errors thrown by route components and loaders, including fallback behavior when no route error component is configured.

CatchBoundary decided whether it had caught an error from the truthiness
of the thrown value. When a component, beforeLoad or loader threw a falsy
value (`throw undefined`, `null` or `''`), the boundary stored it but the
render check failed, so it re-rendered the same crashing children. React
then escalated the error past that boundary, and since every route
boundary and the global boundary ran the same check, the error skipped
every errorComponent and unmounted the whole tree.
Box the caught error in state, so the presence of the box marks the
boundary as having caught something regardless of the thrown value. The
default ErrorComponent and the global boundary's development warning now
also read `message` defensively, since the value they receive can be
`undefined` or `null`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 43e59564-4adc-41b4-992e-c9e1319b3a60

📥 Commits

Reviewing files that changed from the base of the PR and between 650acb4 and 193139a.

📒 Files selected for processing (4)
  • .changeset/catch-boundary-falsy-errors.md
  • packages/react-router/src/CatchBoundary.tsx
  • packages/react-router/src/Matches.tsx
  • packages/react-router/tests/errorComponent.test.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

CatchBoundary now handles undefined, null, and empty-string throws without losing the caught state. Error logging is defensive, and parameterized tests cover route components, loaders, and global fallback behavior.

Changes

Falsy error handling

Layer / File(s)Summary
Preserve falsy caught values
.changeset/catch-boundary-falsy-errors.md, packages/react-router/src/CatchBoundary.tsx, packages/react-router/src/Matches.tsx
CatchBoundary stores caught state separately from the thrown value. Reset handling and error rendering use the boxed value. Development logging safely formats nullish errors.
Validate route and global boundaries
packages/react-router/tests/errorComponent.test.tsx
Parameterized tests cover falsy values thrown by route components and loaders, including rendering through the global catch boundary when no route error component exists.

Estimated code review effort: 2 (Simple) | ~15 minutes

Merge Risk:⚪ Minimal · up to 19313

Catch boundaries now render configured or global error fallbacks for undefined, null, and empty-string throws instead of re-rendering failing content. The behavior is covered for component and loader failures, with no remaining merge-blocking risk identified.

Suggested reviewers:sheraff

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. (1 skipped: 1 …Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely identifies the main fix: rendering the React Router error component for falsy thrown values.
Description check✅ PassedThe description follows the required template, explains the motivation and implementation, documents testing, completes the checklist, and includes the required changeset declaration.
Linked Issues check✅ PassedThe changes satisfy issue #8123. CatchBoundary now tracks caught state independently of the thrown value, reset paths clear that state, defensive error handling prevents fallback failures, and tests c…
Out of Scope Changes check✅ PassedThe changes remain within the scope of issue #8123. The CatchBoundary fix, defensive error formatting, tests, and changeset directly support the requested behavior. Related lazy-route and Vue Router i…
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CatchBoundary: falsy thrown values (undefined, null, '') bypass every errorComponent and unmount the whole app

1 participant

@vladavoX