fix(router-core): don't resolve aborted match to success without loaderData#7673
fix(router-core): don't resolve aborted match to success without loaderData#7673anonrig wants to merge 1 commit into
Conversation
…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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe ChangesAborted Loader Error Resolution Fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Summary
A route's
componentonly renders when its match hasstatus === 'success', which is exactly whyuseLoaderData()is typed as non-undefined. However, when a loader is aborted mid-flight before producing data, the match was promoted frompendingtosuccesswithoutloaderDatabeing set. The component then rendered whileuseLoaderData()returnedundefinedat runtime, despite the type guaranteeing a value — leading to crashes such asCannot read properties of undefined.Root cause
The
AbortErrorbranch inrunLoader(packages/router-core/src/load-matches.ts) did:For a first load (
pending, no data yet) this fabricated asuccessstate withloaderData === 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
AbortErrorbranch, the three cases are now distinguished:abortControllerfired (rapid navigation)pending)status: 'error'(renders theerrorComponent)This restores the invariant
success ⟹ loaderData defined, souseLoaderData()inside a route component is genuinely non-undefined at both the type and runtime level.Behavior change
Previously a loader that threw
AbortErroron initial load rendered the route component withloaderDataundefined (see #4570). It now renders theerrorComponentinstead — 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
throw abortError from loader upon initial load with basepathin bothreact-routerandsolid-routerto assert the error boundary renders instead of the component.aborted loader does not render the route component with undefined loaderDatain bothreact-routerandsolid-router, where the component readsloaderData.router-core,react-router,solid-router, andvue-router; eslint is clean.Summary by CodeRabbit