diff --git a/.changeset/partial-hydration-no-loader.md b/.changeset/partial-hydration-no-loader.md new file mode 100644 index 0000000000..b0e5be1116 --- /dev/null +++ b/.changeset/partial-hydration-no-loader.md @@ -0,0 +1,5 @@ +--- +"@remix-run/router": patch +--- + +Fix a `future.v7_partialHydration` bug that would consider the router uninitialized if a route did not have a loader diff --git a/packages/router/__tests__/route-fallback-test.ts b/packages/router/__tests__/route-fallback-test.ts index 1d8265be45..6e27618334 100644 --- a/packages/router/__tests__/route-fallback-test.ts +++ b/packages/router/__tests__/route-fallback-test.ts @@ -515,4 +515,45 @@ describe("future.v7_partialHydration", () => { consoleWarnSpy.mockReset(); }); }); + + it("does not kick off initial data load for routes that don't have loaders", async () => { + let consoleWarnSpy = jest + .spyOn(console, "warn") + .mockImplementation(() => {}); + let parentDfd = createDeferred(); + let parentSpy = jest.fn(() => parentDfd.promise); + let router = createRouter({ + history: createMemoryHistory({ initialEntries: ["/child"] }), + routes: [ + { + path: "/", + loader: parentSpy, + children: [ + { + path: "child", + }, + ], + }, + ], + future: { + v7_partialHydration: true, + }, + hydrationData: { + loaderData: { + "0": "PARENT DATA", + }, + }, + }); + expect(router.state).toMatchObject({ + // already initialized so calling initialize() won't kick off loaders + initialized: true, + navigation: IDLE_NAVIGATION, + loaderData: { + "0": "PARENT DATA", + }, + }); + + router.dispose(); + consoleWarnSpy.mockReset(); + }); }); diff --git a/packages/router/router.ts b/packages/router/router.ts index 38d5034cda..951aa1b992 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -833,11 +833,17 @@ export function createRouter(init: RouterInit): Router { // were marked for explicit hydration let loaderData = init.hydrationData ? init.hydrationData.loaderData : null; let errors = init.hydrationData ? init.hydrationData.errors : null; - let isRouteInitialized = (m: AgnosticDataRouteMatch) => - m.route.loader && - m.route.loader.hydrate !== true && - ((loaderData && loaderData[m.route.id] !== undefined) || - (errors && errors[m.route.id] !== undefined)); + let isRouteInitialized = (m: AgnosticDataRouteMatch) => { + // No loader, nothing to initialize + if (!m.route.loader) return true; + // Explicitly opting-in to running on hydration + if (m.route.loader.hydrate === true) return false; + // Otherwise, initialized if hydrated with data or an error + return ( + (loaderData && loaderData[m.route.id] !== undefined) || + (errors && errors[m.route.id] !== undefined) + ); + }; // If errors exist, don't consider routes below the boundary if (errors) {