Skip to content

Commit 0e87f65

Browse files
committed
Fig bug withclientLoader.hydrate when hydrating with bubbled errors
1 parent 8afda97 commit 0e87f65

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/react": patch
3+
---
4+
5+
Fix bug with `clientLoader.hydrate` in a layout route when hydrating with bubbled errors

integration/client-data-test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,121 @@ test.describe("Client Data", () => {
846846
expect(html).not.toMatch("Should not see me");
847847
console.error = _consoleError;
848848
});
849+
850+
test("bubbled server loader errors are persisted for hydrating routes", async ({
851+
page,
852+
}) => {
853+
let _consoleError = console.error;
854+
console.error = () => {};
855+
appFixture = await createAppFixture(
856+
await createFixture(
857+
{
858+
files: {
859+
...getFiles({
860+
parentClientLoader: false,
861+
parentClientLoaderHydrate: false,
862+
childClientLoader: false,
863+
childClientLoaderHydrate: false,
864+
}),
865+
"app/routes/parent.tsx": js`
866+
import { json } from '@remix-run/node'
867+
import { Outlet, useLoaderData, useRouteLoaderData, useRouteError } from '@remix-run/react'
868+
869+
export function loader() {
870+
return json({ message: 'Parent Server Loader'});
871+
}
872+
873+
export async function clientLoader({ serverLoader }) {
874+
console.log('running parent client loader')
875+
// Need a small delay to ensure we capture the server-rendered
876+
// fallbacks for assertions
877+
await new Promise(r => setTimeout(r, 100));
878+
let data = await serverLoader();
879+
return { message: data.message + " (mutated by client)" };
880+
}
881+
882+
clientLoader.hydrate = true;
883+
884+
export default function Component() {
885+
let data = useLoaderData();
886+
return (
887+
<>
888+
<p id="parent-data">{data.message}</p>
889+
<Outlet/>
890+
</>
891+
);
892+
}
893+
894+
export function ErrorBoundary() {
895+
let data = useRouteLoaderData("routes/parent")
896+
let error = useRouteError();
897+
return (
898+
<>
899+
<h1>Parent Error</h1>
900+
<p id="parent-data">{data?.message}</p>
901+
<p id="parent-error">{error?.data?.message}</p>
902+
</>
903+
);
904+
}
905+
`,
906+
"app/routes/parent.child.tsx": js`
907+
import { json } from '@remix-run/node'
908+
import { useRouteError, useLoaderData } from '@remix-run/react'
909+
910+
export function loader() {
911+
throw json({ message: 'Child Server Error'});
912+
}
913+
914+
export function clientLoader() {
915+
console.log('running child client loader')
916+
return "Should not see me";
917+
}
918+
919+
clientLoader.hydrate = true;
920+
921+
export default function Component() {
922+
let data = useLoaderData()
923+
return (
924+
<>
925+
<p>Should not see me</p>
926+
<p>{data}</p>;
927+
</>
928+
);
929+
}
930+
`,
931+
},
932+
},
933+
ServerMode.Development // Avoid error sanitization
934+
),
935+
ServerMode.Development // Avoid error sanitization
936+
);
937+
let app = new PlaywrightFixture(appFixture, page);
938+
939+
let logs: string[] = [];
940+
page.on("console", (msg) => logs.push(msg.text()));
941+
942+
await app.goto("/parent/child", false);
943+
let html = await app.getHtml("main");
944+
expect(html).toMatch("Parent Server Loader</p>");
945+
expect(html).toMatch("Child Server Error");
946+
expect(html).not.toMatch("Should not see me");
947+
948+
// Ensure we hydrate and remain on the boundary
949+
await page.waitForSelector(
950+
":has-text('Parent Server Loader (mutated by client)')"
951+
);
952+
html = await app.getHtml("main");
953+
expect(html).toMatch("Parent Server Loader (mutated by client)</p>");
954+
expect(html).toMatch("Child Server Error");
955+
expect(html).not.toMatch("Should not see me");
956+
957+
expect(logs).toEqual([
958+
expect.stringContaining("Download the React DevTools"),
959+
"running parent client loader",
960+
]);
961+
962+
console.error = _consoleError;
963+
});
849964
});
850965

851966
test.describe("clientLoader - lazy route module", () => {

packages/remix-react/routes.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,13 @@ export function createClientRoutes(
354354

355355
// On the first call, resolve with the server result
356356
if (isHydrationRequest) {
357+
if (initialData !== undefined) {
358+
return initialData;
359+
}
357360
if (initialError !== undefined) {
358361
throw initialError;
359362
}
360-
return initialData;
363+
return null;
361364
}
362365

363366
// Call the server loader for client-side navigations

0 commit comments

Comments
 (0)