Skip to content

Commit 6b22f91

Browse files
authored
Preserve hydrated errors during partial hydration (#11305)
1 parent 362115a commit 6b22f91

File tree

3 files changed

+101
-2
lines changed

3 files changed

+101
-2
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Preserve hydrated errors during partial hydration runs

packages/react-router-dom/__tests__/partial-hydration-test.tsx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import "@testing-library/jest-dom";
2-
import { render, screen, waitFor } from "@testing-library/react";
2+
import { act, render, screen, waitFor } from "@testing-library/react";
33
import * as React from "react";
44
import type { LoaderFunction } from "react-router";
55
import { RouterProvider as ReactRouter_RouterPRovider } from "react-router";
@@ -650,4 +650,89 @@ function testPartialHydration(
650650
</div>"
651651
`);
652652
});
653+
654+
it("preserves hydrated errors for non-hydrating loaders", async () => {
655+
let dfd = createDeferred();
656+
let rootSpy: LoaderFunction = jest.fn(() => dfd.promise);
657+
rootSpy.hydrate = true;
658+
659+
let indexSpy = jest.fn();
660+
661+
let router = createTestRouter(
662+
[
663+
{
664+
id: "root",
665+
path: "/",
666+
loader: rootSpy,
667+
Component() {
668+
let data = useLoaderData() as string;
669+
return (
670+
<>
671+
<h1>{`Home - ${data}`}</h1>
672+
<Outlet />
673+
</>
674+
);
675+
},
676+
children: [
677+
{
678+
id: "index",
679+
index: true,
680+
loader: indexSpy,
681+
Component() {
682+
let data = useLoaderData() as string;
683+
return <h2>{`Index - ${data}`}</h2>;
684+
},
685+
ErrorBoundary() {
686+
let error = useRouteError() as string;
687+
return <p>{error}</p>;
688+
},
689+
},
690+
],
691+
},
692+
],
693+
{
694+
hydrationData: {
695+
loaderData: {
696+
root: "HYDRATED ROOT",
697+
},
698+
errors: {
699+
index: "INDEX ERROR",
700+
},
701+
},
702+
future: {
703+
v7_partialHydration: true,
704+
},
705+
}
706+
);
707+
let { container } = render(<RouterProvider router={router} />);
708+
709+
expect(getHtml(container)).toMatchInlineSnapshot(`
710+
"<div>
711+
<h1>
712+
Home - HYDRATED ROOT
713+
</h1>
714+
<p>
715+
INDEX ERROR
716+
</p>
717+
</div>"
718+
`);
719+
720+
expect(router.state.initialized).toBe(false);
721+
722+
await act(() => dfd.resolve("UPDATED ROOT"));
723+
724+
expect(getHtml(container)).toMatchInlineSnapshot(`
725+
"<div>
726+
<h1>
727+
Home - UPDATED ROOT
728+
</h1>
729+
<p>
730+
INDEX ERROR
731+
</p>
732+
</div>"
733+
`);
734+
735+
expect(rootSpy).toHaveBeenCalledTimes(1);
736+
expect(indexSpy).not.toHaveBeenCalled();
737+
});
653738
}

packages/router/router.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@ export function createRouter(init: RouterInit): Router {
17181718
// preserving any new action data or existing action data (in the case of
17191719
// a revalidation interrupting an actionReload)
17201720
// If we have partialHydration enabled, then don't update the state for the
1721-
// initial data load since iot's not a "navigation"
1721+
// initial data load since it's not a "navigation"
17221722
if (
17231723
!isUninterruptedRevalidation &&
17241724
(!future.v7_partialHydration || !initialHydration)
@@ -1835,6 +1835,15 @@ export function createRouter(init: RouterInit): Router {
18351835
});
18361836
});
18371837

1838+
// During partial hydration, preserve SSR errors for routes that don't re-run
1839+
if (future.v7_partialHydration && initialHydration && state.errors) {
1840+
Object.entries(state.errors)
1841+
.filter(([id]) => !matchesToLoad.some((m) => m.route.id === id))
1842+
.forEach(([routeId, error]) => {
1843+
errors = Object.assign(errors || {}, { [routeId]: error });
1844+
});
1845+
}
1846+
18381847
let updatedFetchers = markFetchRedirectsDone();
18391848
let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
18401849
let shouldUpdateFetchers =

0 commit comments

Comments
 (0)