diff --git a/.changeset/cold-schools-relate.md b/.changeset/cold-schools-relate.md new file mode 100644 index 0000000000..d45d6f8fb1 --- /dev/null +++ b/.changeset/cold-schools-relate.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Fix clientLoader.hydrate when an ancestor route is also hydrating a clientLoader diff --git a/packages/react-router/__tests__/dom/partial-hydration-test.tsx b/packages/react-router/__tests__/dom/partial-hydration-test.tsx index 9b61ac66ba..6ec3501380 100644 --- a/packages/react-router/__tests__/dom/partial-hydration-test.tsx +++ b/packages/react-router/__tests__/dom/partial-hydration-test.tsx @@ -66,9 +66,6 @@ describe("Partial Hydration Behavior", () => { }, ], { - future: { - v7_partialHydration: true, - }, patchRoutesOnNavigation({ path, patch }) { if (path === "/parent/child") { patch("parent", [ @@ -155,9 +152,6 @@ describe("Partial Hydration Behavior", () => { }, ], { - future: { - v7_partialHydration: true, - }, patchRoutesOnNavigation({ path, patch }) { if (path === "/parent/child") { patch("parent", [ @@ -248,9 +242,6 @@ describe("Partial Hydration Behavior", () => { }, ], { - future: { - v7_partialHydration: true, - }, async patchRoutesOnNavigation({ path, patch }) { await patchDfd.promise; if (path === "/parent/child") { @@ -853,4 +844,76 @@ function testPartialHydration( expect(rootSpy).toHaveBeenCalledTimes(1); expect(indexSpy).not.toHaveBeenCalled(); }); + + it("renders child fallback when ancestor route has hydration data and a hydrating loader", async () => { + let rootDfd = createDeferred(); + let rootLoader: LoaderFunction = () => rootDfd.promise; + rootLoader.hydrate = true; + let indexDfd = createDeferred(); + let indexLoader: LoaderFunction = () => indexDfd.promise; + indexLoader.hydrate = true; + let router = createTestRouter( + [ + { + id: "root", + path: "/", + loader: rootLoader, + Component() { + let data = useLoaderData() as string; + return ( + <> +

{`Home - ${data}`}

+ + + ); + }, + children: [ + { + id: "index", + index: true, + loader: indexLoader, + HydrateFallback: () =>

Index Loading...

, + Component() { + let data = useLoaderData() as string; + return

{`Index - ${data}`}

; + }, + }, + ], + }, + ], + { + hydrationData: { + loaderData: { + root: "HYDRATED ROOT", + }, + }, + }, + ); + let { container } = render(); + + expect(getHtml(container)).toMatchInlineSnapshot(` + "
+

+ Home - HYDRATED ROOT +

+

+ Index Loading... +

+
" + `); + + rootDfd.resolve("ROOT UPDATED"); + indexDfd.resolve("INDEX UPDATED"); + await waitFor(() => screen.getByText(/INDEX UPDATED/)); + expect(getHtml(container)).toMatchInlineSnapshot(` + "
+

+ Home - ROOT UPDATED +

+

+ Index - INDEX UPDATED +

+
" + `); + }); } diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index b2348c0c6b..42479c0940 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -1024,11 +1024,14 @@ export function createRouter(init: RouterInit): Router { } // Toggle renderFallback based on per-route values + // Using a `.forEach` is important instead of something like an `.every` + // here because we need to evaluate renderFallback for all matches renderFallback = false; - initialized = relevantMatches.every((m) => { + initialized = true; + relevantMatches.forEach((m) => { let status = getRouteHydrationStatus(m.route, loaderData, errors); renderFallback = renderFallback || status.renderFallback; - return !status.shouldLoad; + initialized = initialized && !status.shouldLoad; }); } }