Skip to content

fix(router-core): don't resolve aborted match to success without loaderData#7673

Open
anonrig wants to merge 1 commit into
TanStack:mainfrom
anonrig:fix/aborted-loader-undefined-loaderdata
Open

fix(router-core): don't resolve aborted match to success without loaderData#7673
anonrig wants to merge 1 commit into
TanStack:mainfrom
anonrig:fix/aborted-loader-undefined-loaderdata

Conversation

@anonrig

@anonrig anonrig commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

A route's component only renders when its match has status === 'success', which is exactly why useLoaderData() is typed as non-undefined. However, when a loader is aborted mid-flight before producing data, the match was promoted from pending to success without loaderData being set. The component then rendered while useLoaderData() returned undefined at runtime, despite the type guaranteeing a value — leading to crashes such as Cannot read properties of undefined.

Root cause

The AbortError branch in runLoader (packages/router-core/src/load-matches.ts) did:

status: prev.status === 'pending' ? 'success' : prev.status

For a first load (pending, no data yet) this fabricated a success state with loaderData === undefined, breaking the invariant that a successful match always carries loader data. This branch was introduced in #4570 to avoid flashing the error boundary on a spontaneous abort.

Fix

In the AbortError branch, the three cases are now distinguished:

Case Behavior
The match's own abortController fired (rapid navigation) Bail out without mutating state (unchanged, from #6426)
The loader aborted but a previous load already produced data Keep showing it — stale-while-revalidate (unchanged)
The loader aborted with no data yet (pending) Fall through to the normal error handling → status: 'error' (renders the errorComponent)

This restores the invariant success ⟹ loaderData defined, so useLoaderData() inside a route component is genuinely non-undefined at both the type and runtime level.

Behavior change

Previously a loader that threw AbortError on initial load rendered the route component with loaderData undefined (see #4570). It now renders the errorComponent instead — the designed mechanism for "no data available." The rapid-navigation abort path (#6426, reproducer for #6388) is unaffected, and stale-while-revalidate is preserved.

Tests

  • Updated throw abortError from loader upon initial load with basepath in both react-router and solid-router to assert the error boundary renders instead of the component.
  • Added aborted loader does not render the route component with undefined loaderData in both react-router and solid-router, where the component reads loaderData.
  • Full unit + type suites pass for router-core, react-router, solid-router, and vue-router; eslint is clean.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed the behavior of aborted loaders to properly resolve as errors. Routes with interrupted loaders now correctly display the error component instead of rendering with undefined loader data.

…erData

When a loader is aborted before producing data, the match was promoted from `pending` to `success` without setting `loaderData`. The route component then rendered while `useLoaderData()` returned `undefined`, violating its non-undefined type. Resolve such a match to `error` instead so the errorComponent renders. Stale-while-revalidate (aborts on an already-loaded match) and the rapid-navigation abort path are preserved.
@coderabbitai

coderabbitai Bot commented Jun 22, 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1acb4a3-3f7b-45bb-aabf-8bb456d60c2e

📥 Commits

Reviewing files that changed from the base of the PR and between 279a849 and 8ec67d4.

📒 Files selected for processing (4)
  • .changeset/fix-aborted-loader-undefined-loaderdata.md
  • packages/react-router/tests/loaders.test.tsx
  • packages/router-core/src/load-matches.ts
  • packages/solid-router/tests/loaders.test.tsx

📝 Walkthrough

Walkthrough

The runLoader abort handler in load-matches.ts is changed so that when an AbortError is caught and the match is still pending (no loader data produced), it falls through to normal error handling instead of forcing a success state. Existing loader tests and a new regression test in both react-router and solid-router verify the errorComponent renders and the route component does not.

Changes

Aborted Loader Error Resolution Fix

Layer / File(s) Summary
AbortError handling in runLoader
packages/router-core/src/load-matches.ts
When a loader is aborted while the match is still pending, the code no longer unconditionally transitions to success. Non-pending matches preserve existing data by only clearing isFetching; pending matches fall through to the error path.
Tests and changeset
packages/react-router/tests/loaders.test.tsx, packages/solid-router/tests/loaders.test.tsx, .changeset/fix-aborted-loader-undefined-loaderdata.md
Existing abort test assertions are inverted to expect errorComponent output and absence of route content. New regression tests cover the case where a loader rejects with AbortError before producing data. Changeset documents the @tanstack/router-core patch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested labels

package: react-router, package: router-core

Poem

🐰 Hop hop, the loader bailed out fast,
No data arrived—the match was aghast!
No longer "success" when nothing came through,
Now "error" it stays, as it always was true.
The errorComponent shines—hurrah!
Undefined loader data? Not on my paw. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing a bug where aborted loaders were incorrectly transitioning to success status without loaderData, which is the core issue addressed across all file changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

Sign up for free to 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.

1 participant