Summary
When a route exports HydrateFallback and has an async loader (a client loader returning a promise, or a server loader), its ErrorBoundary receives Juniper's internal deferred wrapper { promise } as loaderData instead of the loader's data.
Where
src/_client.tsx, createRoute: when HydrateFallback is set, the loader returns { promise: result } (the loader wrapper around line 540). Component unwraps it with <Await resolve={loaderData.promise}>. The ErrorBoundary shim (around line 622) passes useLoaderData() straight through with no unwrap.
Reproduction
With a layout that has HydrateFallback, an async loader and an ErrorBoundary, and a child whose loader throws:
createRoutesStub([{
path: "/t",
loader: async () => ({ name: "acme" }),
HydrateFallback: () => <p>Loading</p>,
default: () => <Outlet />,
ErrorBoundary: ({ loaderData }) => <p>{JSON.stringify(Object.keys(loaderData ?? {}))}</p>,
children: [{ path: "u", loader: () => { throw new Error("x"); }, default: () => <p>U</p> }],
}]);
At /t/u the boundary renders ["promise"]. children on createRoutesStub is the nested-route support in the udibo#975 fix.
Impact
ErrorBoundaryProps<Params, LoaderData>["loaderData"] is typed LoaderData | undefined (after the udibo#979 fix). With this defect the value is a third shape, { promise }. A boundary that checks if (loaderData) and then reads a field gets undefined fields, or crashes on nested access, in the case it exists to handle: a descendant failed while this route's data was fine.
Found
While fixing udibo#979 and udibo#975. It was left out of that PR because it is a change to the boundary shim in src/_client.tsx, not a type or test utility change.
Summary
When a route exports
HydrateFallbackand has an async loader (a client loader returning a promise, or a server loader), itsErrorBoundaryreceives Juniper's internal deferred wrapper{ promise }asloaderDatainstead of the loader's data.Where
src/_client.tsx,createRoute: whenHydrateFallbackis set, the loader returns{ promise: result }(theloaderwrapper around line 540).Componentunwraps it with<Await resolve={loaderData.promise}>. TheErrorBoundaryshim (around line 622) passesuseLoaderData()straight through with no unwrap.Reproduction
With a layout that has
HydrateFallback, an async loader and anErrorBoundary, and a child whose loader throws:At
/t/uthe boundary renders["promise"].childrenoncreateRoutesStubis the nested-route support in the udibo#975 fix.Impact
ErrorBoundaryProps<Params, LoaderData>["loaderData"]is typedLoaderData | undefined(after the udibo#979 fix). With this defect the value is a third shape,{ promise }. A boundary that checksif (loaderData)and then reads a field getsundefinedfields, or crashes on nested access, in the case it exists to handle: a descendant failed while this route's data was fine.Found
While fixing udibo#979 and udibo#975. It was left out of that PR because it is a change to the boundary shim in
src/_client.tsx, not a type or test utility change.