fix: type boundary loaderData as optional and nest route stubs - #160
Merged
Merged
Conversation
ErrorBoundaryProps now types loaderData and actionData as possibly undefined, matching React Router, and createRoutesStub accepts children and index so a layout boundary can be tested against a child failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KyleJune
pushed a commit
that referenced
this pull request
Sep 24, 2026
## [0.16.6](0.16.5...0.16.6) (2026-09-24) ### Bug Fixes * type boundary loaderData as optional and nest route stubs ([#160](#160)) ([80c7320](80c7320))
|
🎉 This PR is included in version 0.16.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two testing and type defects around route error boundaries. Both were found while fixing a udibo layout boundary (udibo/udibo#973) and are tracked as udibo/udibo#979 and udibo/udibo#975.
ErrorBoundaryPropstypedloaderDataas always present (udibo/udibo#979). It isundefinedwhen the boundary's own loader threw. React Router 8.3.1 handles this inprocessRouteLoaderData, which writesResetLoaderDataSymbolfor that route, and inmergeLoaderData, which filters the symbol out and doesn't carry the previous navigation's value forward. The static handler used for SSR never sets it. It is alsoundefinedwhen the route has no loader, when middleware refused the request, and during SSR of a no-JS form submission that failed at or below the route.loaderDataandactionDataare nowLoaderData | undefinedandActionData | undefinedonErrorBoundaryProps, so the compiler makes a boundary check them before reading.createRoutesStubcould only build a flat router (udibo/udibo#975). A layoutErrorBoundarycould only be reached by failing the layout's own loader, never a child's.RouteStubnow takeschildrenandindex, and each nested route goes through the samecreateRouteboundary shim production uses, so the stub doesn't need a copy of it. This also covers the second item in udibo/udibo#979, the request for an exported error-normalization helper. Tests no longer hand-build a nested router that has to copy the shim'sisRouteErrorResponse→HttpErrorstep; the stub runs the real one.Type change: this can break existing code
This is titled
fix:because the old type was wrong, but it can make existing code fail to typecheck. A boundary that readloaderData.someField(oractionData.x) without a check now gets a type error. Every such error points at a real crash in the case the boundary exists for.ErrorBoundaryPropsno longerextends RoutePropsdirectly. It extendsOmit<RouteProps, "loaderData" | "actionData">and widens those two fields. Nothing in this repo's example, templates or tutorial needed a change. udibo also typechecks cleanly against this branch, because its two boundaries that readloaderDataalready declared| undefinedthemselves.The flat
createRoutesStubAPI is unchanged: top-level routes still default to path/and remain siblings.Changes
src/mod.ts:ErrorBoundaryProps.loaderData/actionDataare now| undefined. The JSDoc says when each is absent.src/utils/testing.ts:RouteStub.childrenandRouteStub.index, built recursively throughcreateRoute.pathis relative to its parent. A child with neitherpathnorindexis a pathless layout.TypeErrorwhen the stub is created.hydrationDataids ("0-0").docs/error-handling.md: theErrorBoundaryPropsblock now matches the type. A new layout-boundary example keeps its navigation when only a child failed. The nested-boundaries section links to testing.docs/testing.md: covers nested stubs and adds a "Testing Error Boundaries" example. It also notes that the stub has no production catch-all 404 route.Testing
src/mod.test.tsx(new):assertType<IsExact<…>>on both fields, plus a@ts-expect-erroron an unguardedloaderData.name.undefinedundefined, not carried forwardClient+createServer: own loader threw, descendant loader threw, and a no-JS POST whose child action threw.src/utils/testing.test.tsx: a new "createRoutesStub with nested routes" block covers:data()response reaching the boundary asHttpErrorhydrationDataidsrouteIdreaching its server loader requestloaderDatatest failed against the unfixed stub (--no-check, 13 steps). The type tests failed to typecheck against the unfixed type.loaderData: LoaderData | undefined→LoaderData:mod.test.tsxfails typecheck with TS2345 at theIsExactcheck and TS2578 for the unused@ts-expect-error.children: undefined, i.e. a flat stub): every nested test fails. The layout-loader case fails at the intended assertion, rendering404: Error: No route matches URL "/tenants/acme/users"andNo layout datainstead of404: User not found.deno task check,deno task test(58 files, 700 steps) anddeno task test:example(33 files, 329 steps) all pass.actionDatascoped to "this route", and the missing catch-all 404 route. The no-JS case is now pinned by the SSR POST test.Related, not fixed here: when a route has
HydrateFallbackand an async loader, itsErrorBoundarygets the internal{ promise }wrapper asloaderData. That is a change to thesrc/_client.tsxboundary shim, filed as #158.Closes
Nothing in this repo. The two udibo issues this fixes, udibo/udibo#979 and udibo/udibo#975, stay open until udibo adopts the release.
🤖 Generated with Claude Code