diff --git a/packages/react-router/.changes/patch.preserve-spa-lazy-route-errors.md b/packages/react-router/.changes/patch.preserve-spa-lazy-route-errors.md new file mode 100644 index 0000000000..e1938e4038 --- /dev/null +++ b/packages/react-router/.changes/patch.preserve-spa-lazy-route-errors.md @@ -0,0 +1 @@ +Preserve lazy route module import errors during SPA navigations instead of replacing them with a missing `dataStrategy` result error diff --git a/packages/react-router/__tests__/router/data-strategy-test.ts b/packages/react-router/__tests__/router/data-strategy-test.ts index 9f7786f379..9bba9f4295 100644 --- a/packages/react-router/__tests__/router/data-strategy-test.ts +++ b/packages/react-router/__tests__/router/data-strategy-test.ts @@ -3,6 +3,7 @@ import type { DataStrategyMatch, DataStrategyResult, } from "../../lib/router/utils"; +import { getSingleFetchDataStrategyImpl } from "../../lib/dom/ssr/single-fetch"; import { createDeferred, createAsyncStub, @@ -173,6 +174,37 @@ describe("router dataStrategy", () => { ); }); + it("preserves lazy route errors in the non-SSR single-fetch strategy", async () => { + let lazyError = new Error("Unable to load lazy route module"); + let [lazy, lazyDeferred] = createAsyncStub(); + let t = setup({ + routes: [ + { + path: "/", + }, + { + id: "lazy", + path: "/lazy", + lazy, + ErrorBoundary: () => null, + }, + ], + dataStrategy: getSingleFetchDataStrategyImpl( + () => t.router, + () => ({ hasLoader: false, hasClientLoader: false }), + async () => { + throw new Error("Unexpected single-fetch request"); + }, + false, + ), + }); + + await t.navigate("/lazy"); + await lazyDeferred.reject(lazyError); + + expect(t.router.state.errors).toEqual({ lazy: lazyError }); + }); + it("should allow custom implementations to override default behavior", async () => { let t = setup({ routes: [ diff --git a/packages/react-router/lib/dom/ssr/single-fetch.tsx b/packages/react-router/lib/dom/ssr/single-fetch.tsx index 9cfbef89e2..cf0afb19b0 100644 --- a/packages/react-router/lib/dom/ssr/single-fetch.tsx +++ b/packages/react-router/lib/dom/ssr/single-fetch.tsx @@ -314,26 +314,21 @@ async function nonSsrStrategy( let matchesToLoad = args.matches.filter((m) => m.shouldCallHandler()); let results: Record = {}; await Promise.all( - matchesToLoad.map((m) => - m.resolve(async (handler) => { - try { - let { hasClientLoader } = getRouteInfo(m); - // Need to pass through a `singleFetch` override handler so - // clientLoader's can still call server loaders through `.data` - // requests - let routeId = m.route.id; - let result = hasClientLoader - ? await handler(async () => { - let { data } = await fetchAndDecode(args, [routeId]); - return unwrapSingleFetchResult(data, routeId); - }) - : await handler(); - results[m.route.id] = { type: "data", result }; - } catch (e) { - results[m.route.id] = { type: "error", result: e }; - } - }), - ), + matchesToLoad.map(async (m) => { + let routeId = m.route.id; + results[routeId] = await m.resolve(async (handler) => { + let { hasClientLoader } = getRouteInfo(m); + // Need to pass through a `singleFetch` override handler so + // clientLoader's can still call server loaders through `.data` + // requests + return hasClientLoader + ? handler(async () => { + let { data } = await fetchAndDecode(args, [routeId]); + return unwrapSingleFetchResult(data, routeId); + }) + : handler(); + }); + }), ); return results; }