Skip to content

Commit

Permalink
Proxy server loader errors through serverLoader during hydration (rem…
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 authored Dec 15, 2023
1 parent db4471d commit 9edcf88
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/server-loader-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/react": patch
---

Propagate server `loader` errors through `serverLoader` in hydrating `clientLoader`'s
56 changes: 56 additions & 0 deletions integration/client-data-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect } from "@playwright/test";

import { ServerMode } from "../packages/remix-server-runtime/mode.js";
import {
createAppFixture,
createFixture,
Expand Down Expand Up @@ -724,6 +725,61 @@ test.describe("Client Data", () => {
html = await app.getHtml("main");
expect(html).toMatch("Child Server Loader Data (2+) (mutated by client)");
});

test("server loader errors are re-thrown from serverLoader()", async ({
page,
}) => {
let _consoleError = console.error;
console.error = () => {};
appFixture = await createAppFixture(
await createFixture(
{
files: {
...getFiles({
parentClientLoader: false,
parentClientLoaderHydrate: false,
childClientLoader: false,
childClientLoaderHydrate: false,
}),
"app/routes/parent.child.tsx": js`
import { ClientLoaderFunctionArgs, useRouteError } from "@remix-run/react";
export function loader() {
throw new Error("Broken!")
}
export async function clientLoader({ serverLoader }) {
return await serverLoader();
}
clientLoader.hydrate = true;
export default function Index() {
return <h1>Should not see me</h1>;
}
export function ErrorBoundary() {
let error = useRouteError();
return <p id="child-error">{error.message}</p>;
}
`,
},
},
ServerMode.Development // Avoid error sanitization
),
ServerMode.Development // Avoid error sanitization
);
let app = new PlaywrightFixture(appFixture, page);

await app.goto("/parent/child");
let html = await app.getHtml("main");
expect(html).toMatch("Broken!");
// Ensure we hydrate and remain on the boundary
await new Promise((r) => setTimeout(r, 100));
html = await app.getHtml("main");
expect(html).toMatch("Broken!");
expect(html).not.toMatch("Should not see me");
console.error = _consoleError;
});
});

test.describe("clientLoader - lazy route module", () => {
Expand Down
15 changes: 7 additions & 8 deletions packages/remix-react/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,11 @@ export function createClientRoutes(
: routeModule.shouldRevalidate,
});

let initialData =
initialState &&
initialState.loaderData &&
initialState.loaderData[route.id];

let initialData = initialState?.loaderData?.[route.id];
let initialError = initialState?.errors?.[route.id];
let isHydrationRequest =
needsRevalidation == null &&
routeModule.clientLoader != null &&
(routeModule.clientLoader.hydrate === true || !route.hasLoader);
(routeModule.clientLoader?.hydrate === true || !route.hasLoader);

dataRoute.loader = async ({ request, params }: LoaderFunctionArgs) => {
try {
Expand All @@ -253,8 +249,11 @@ export function createClientRoutes(
throw getNoServerHandlerError("loader", route.id);
}

// On the first call, resolve with the pre-loaded server data
// On the first call, resolve with the server result
if (isHydrationRequest) {
if (initialError !== undefined) {
throw initialError;
}
return initialData;
}

Expand Down

0 comments on commit 9edcf88

Please sign in to comment.