From 06683e6fed1c5fdee6661b423cc86ebf8e1da25e Mon Sep 17 00:00:00 2001
From: Matt Brophy
Date: Wed, 6 Dec 2023 10:11:37 -0500
Subject: [PATCH 1/3] Slight refactor to partial hydration to leverage
state.initialized properly
---
.../__tests__/data-browser-router-test.tsx | 413 --------------
.../__tests__/partial-hydration-test.tsx | 524 ++++++++++++++++++
packages/react-router-dom/index.tsx | 5 +-
packages/react-router/lib/components.tsx | 5 +-
.../router/__tests__/route-fallback-test.ts | 8 +-
packages/router/router.ts | 43 +-
6 files changed, 561 insertions(+), 437 deletions(-)
create mode 100644 packages/react-router-dom/__tests__/partial-hydration-test.tsx
diff --git a/packages/react-router-dom/__tests__/data-browser-router-test.tsx b/packages/react-router-dom/__tests__/data-browser-router-test.tsx
index 16b195c9ef..9a3571fdf5 100644
--- a/packages/react-router-dom/__tests__/data-browser-router-test.tsx
+++ b/packages/react-router-dom/__tests__/data-browser-router-test.tsx
@@ -7464,419 +7464,6 @@ function testDomRouter(
expect(spy).toHaveBeenCalledTimes(2);
});
});
-
- // TODO: Probably want these running against RouterProvider in react-router too?
- // Look into extracting the setState stuff and sharing the subscriber,
- // layout effect, navigator, render stuff
- describe("partial hydration", () => {
- it("does not handle partial hydration by default", async () => {
- let router = createTestRouter(
- [
- {
- id: "root",
- path: "/",
- loader: () => "ROOT",
- Component() {
- let data = useLoaderData() as string;
- return (
-
-
{`Home - ${data}`}
-
-
- );
- },
- children: [
- {
- id: "index",
- index: true,
- loader: () => "INDEX",
- HydrateFallback: () => Should not see me
,
- Component() {
- let data = useLoaderData() as string;
- return {`Index - ${data}`}
;
- },
- },
- ],
- },
- ],
- {
- window: getWindow("/"),
- hydrationData: {
- loaderData: {
- root: "HYDRATED ROOT",
- },
- },
- }
- );
- let { container } = render();
-
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- Index - undefined
-
-
-
"
- `);
- });
-
- it("supports partial hydration w/leaf fallback", async () => {
- let dfd = createDeferred();
- let router = createTestRouter(
- [
- {
- id: "root",
- path: "/",
- loader: () => "ROOT",
- Component() {
- let data = useLoaderData() as string;
- return (
-
-
{`Home - ${data}`}
-
-
- );
- },
- children: [
- {
- id: "index",
- index: true,
- loader: () => dfd.promise,
- HydrateFallback: () => Index Loading...
,
- Component() {
- let data = useLoaderData() as string;
- return {`Index - ${data}`}
;
- },
- },
- ],
- },
- ],
- {
- window: getWindow("/"),
- hydrationData: {
- loaderData: {
- root: "HYDRATED ROOT",
- },
- },
- future: {
- v7_partialHydration: true,
- },
- }
- );
- let { container } = render();
-
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- Index Loading...
-
-
-
"
- `);
-
- dfd.resolve("INDEX DATA");
- await waitFor(() => screen.getByText(/INDEX DATA/));
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- Index - INDEX DATA
-
-
-
"
- `);
- });
-
- it("supports partial hydration w/root fallback", async () => {
- let dfd = createDeferred();
- let router = createTestRouter(
- [
- {
- id: "root",
- path: "/",
- loader: () => "ROOT",
- HydrateFallback: () => Root Loading...
,
- Component() {
- let data = useLoaderData() as string;
- return (
-
-
{`Home - ${data}`}
-
-
- );
- },
- children: [
- {
- id: "index",
- index: true,
- loader: () => dfd.promise,
- Component() {
- let data = useLoaderData() as string;
- return {`Index - ${data}`}
;
- },
- },
- ],
- },
- ],
- {
- window: getWindow("/"),
- hydrationData: {
- loaderData: {
- root: "HYDRATED ROOT",
- },
- },
- future: {
- v7_partialHydration: true,
- },
- }
- );
- let { container } = render();
-
- expect(getHtml(container)).toMatchInlineSnapshot(`
- ""
- `);
-
- dfd.resolve("INDEX DATA");
- await waitFor(() => screen.getByText(/INDEX DATA/));
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- Index - INDEX DATA
-
-
-
"
- `);
- });
-
- it("supports partial hydration w/no fallback", async () => {
- let dfd = createDeferred();
- let router = createTestRouter(
- [
- {
- id: "root",
- path: "/",
- loader: () => "ROOT",
- Component() {
- let data = useLoaderData() as string;
- return (
-
-
{`Home - ${data}`}
-
-
- );
- },
- children: [
- {
- id: "index",
- index: true,
- loader: () => dfd.promise,
- Component() {
- let data = useLoaderData() as string;
- return {`Index - ${data}`}
;
- },
- },
- ],
- },
- ],
- {
- window: getWindow("/"),
- hydrationData: {
- loaderData: {
- root: "HYDRATED ROOT",
- },
- },
- future: {
- v7_partialHydration: true,
- },
- }
- );
- let { container } = render();
-
- expect(getHtml(container)).toMatchInlineSnapshot(`""`);
-
- dfd.resolve("INDEX DATA");
- await waitFor(() => screen.getByText(/INDEX DATA/));
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- Index - INDEX DATA
-
-
-
"
- `);
- });
-
- it("deprecates fallbackElement", async () => {
- let dfd1 = createDeferred();
- let dfd2 = createDeferred();
- let router = createTestRouter(
- [
- {
- id: "root",
- path: "/",
- loader: () => dfd1.promise,
- HydrateFallback: () => Root Loading...
,
- Component() {
- let data = useLoaderData() as string;
- return (
-
-
{`Home - ${data}`}
-
-
- );
- },
- children: [
- {
- id: "index",
- index: true,
- loader: () => dfd2.promise,
- Component() {
- let data = useLoaderData() as string;
- return {`Index - ${data}`}
;
- },
- },
- ],
- },
- ],
- {
- window: getWindow("/"),
- hydrationData: {
- loaderData: {
- root: "HYDRATED ROOT",
- },
- },
- future: {
- v7_partialHydration: true,
- },
- }
- );
- let { container } = render(
- fallbackElement...}
- />
- );
-
- expect(getHtml(container)).toMatchInlineSnapshot(`
- ""
- `);
-
- expect(consoleWarn).toHaveBeenCalledWith(
- "`` is deprecated when using `v7_partialHydration`"
- );
-
- dfd1.resolve("ROOT DATA");
- dfd2.resolve("INDEX DATA");
- await waitFor(() => screen.getByText(/INDEX DATA/));
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- Index - INDEX DATA
-
-
-
"
- `);
- });
-
- it("does not re-run loaders that don't have loader data due to errors", async () => {
- let spy = jest.fn();
- let router = createTestRouter(
- [
- {
- id: "root",
- path: "/",
- loader: () => "ROOT",
- Component() {
- let data = useLoaderData() as string;
- return (
-
-
{`Home - ${data}`}
-
-
- );
- },
- children: [
- {
- id: "index",
- index: true,
- loader: spy,
- HydrateFallback: () => Index Loading...
,
- Component() {
- let data = useLoaderData() as string;
- return {`Index - ${data}`}
;
- },
- ErrorBoundary() {
- let error = useRouteError() as string;
- return {error}
;
- },
- },
- ],
- },
- ],
- {
- window: getWindow("/"),
- hydrationData: {
- loaderData: {
- root: "HYDRATED ROOT",
- },
- errors: {
- index: "INDEX ERROR",
- },
- },
- future: {
- v7_partialHydration: true,
- },
- }
- );
- let { container } = render();
-
- expect(getHtml(container)).toMatchInlineSnapshot(`
- "
-
-
- Home - HYDRATED ROOT
-
-
- INDEX ERROR
-
-
-
"
- `);
-
- expect(spy).not.toHaveBeenCalled();
- });
- });
});
}
diff --git a/packages/react-router-dom/__tests__/partial-hydration-test.tsx b/packages/react-router-dom/__tests__/partial-hydration-test.tsx
new file mode 100644
index 0000000000..49bfe12f8f
--- /dev/null
+++ b/packages/react-router-dom/__tests__/partial-hydration-test.tsx
@@ -0,0 +1,524 @@
+import "@testing-library/jest-dom";
+import { render, screen, waitFor } from "@testing-library/react";
+import * as React from "react";
+import type { LoaderFunction } from "react-router";
+import { RouterProvider as ReactRouter_RouterPRovider } from "react-router";
+import {
+ Outlet,
+ RouterProvider as ReactRouterDom_RouterProvider,
+ createBrowserRouter,
+ createHashRouter,
+ createMemoryRouter,
+ useLoaderData,
+ useRouteError,
+} from "react-router-dom";
+
+import getHtml from "../../react-router/__tests__/utils/getHtml";
+import { createDeferred } from "../../router/__tests__/utils/utils";
+
+let didAssertMissingHydrateFallback = false;
+
+describe("v7_partialHydration", () => {
+ describe("createBrowserRouter", () => {
+ testPartialHydration(createBrowserRouter, ReactRouterDom_RouterProvider);
+ });
+
+ describe("createHashRouter", () => {
+ testPartialHydration(createHashRouter, ReactRouterDom_RouterProvider);
+ });
+
+ describe("createMemoryRouter", () => {
+ testPartialHydration(createMemoryRouter, ReactRouter_RouterPRovider);
+ });
+});
+
+function testPartialHydration(
+ createTestRouter:
+ | typeof createBrowserRouter
+ | typeof createHashRouter
+ | typeof createMemoryRouter,
+ RouterProvider:
+ | typeof ReactRouterDom_RouterProvider
+ | typeof ReactRouter_RouterPRovider
+) {
+ let consoleWarn: jest.SpyInstance;
+
+ beforeEach(() => {
+ consoleWarn = jest.spyOn(console, "warn").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ consoleWarn.mockRestore();
+ });
+
+ it("does not handle partial hydration by default", async () => {
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => "ROOT",
+ Component() {
+ let data = useLoaderData() as string;
+ return (
+ <>
+ {`Home - ${data}`}
+
+ >
+ );
+ },
+ children: [
+ {
+ id: "index",
+ index: true,
+ loader: () => "INDEX",
+ HydrateFallback: () => Should not see me
,
+ 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 - undefined
+
+ "
+ `);
+ });
+
+ it("supports partial hydration w/leaf fallback", async () => {
+ let dfd = createDeferred();
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => "ROOT",
+ Component() {
+ let data = useLoaderData() as string;
+ return (
+ <>
+ {`Home - ${data}`}
+
+ >
+ );
+ },
+ children: [
+ {
+ id: "index",
+ index: true,
+ loader: () => dfd.promise,
+ HydrateFallback: () => Index Loading...
,
+ Component() {
+ let data = useLoaderData() as string;
+ return {`Index - ${data}`}
;
+ },
+ },
+ ],
+ },
+ ],
+ {
+ hydrationData: {
+ loaderData: {
+ root: "HYDRATED ROOT",
+ },
+ },
+ future: {
+ v7_partialHydration: true,
+ },
+ }
+ );
+ let { container } = render();
+
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index Loading...
+
+
"
+ `);
+
+ dfd.resolve("INDEX DATA");
+ await waitFor(() => screen.getByText(/INDEX DATA/));
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index - INDEX DATA
+
+ "
+ `);
+ });
+
+ it("supports partial hydration w/root fallback", async () => {
+ let dfd = createDeferred();
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => "ROOT",
+ HydrateFallback: () => Root Loading...
,
+ Component() {
+ let data = useLoaderData() as string;
+ return (
+ <>
+ {`Home - ${data}`}
+
+ >
+ );
+ },
+ children: [
+ {
+ id: "index",
+ index: true,
+ loader: () => dfd.promise,
+ Component() {
+ let data = useLoaderData() as string;
+ return {`Index - ${data}`}
;
+ },
+ },
+ ],
+ },
+ ],
+ {
+ hydrationData: {
+ loaderData: {
+ root: "HYDRATED ROOT",
+ },
+ },
+ future: {
+ v7_partialHydration: true,
+ },
+ }
+ );
+ let { container } = render();
+
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ ""
+ `);
+
+ dfd.resolve("INDEX DATA");
+ await waitFor(() => screen.getByText(/INDEX DATA/));
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index - INDEX DATA
+
+ "
+ `);
+ });
+
+ it("supports partial hydration w/no fallback", async () => {
+ let dfd = createDeferred();
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => "ROOT",
+ Component() {
+ let data = useLoaderData() as string;
+ return (
+ <>
+ {`Home - ${data}`}
+
+ >
+ );
+ },
+ children: [
+ {
+ id: "index",
+ index: true,
+ loader: () => dfd.promise,
+ Component() {
+ let data = useLoaderData() as string;
+ return {`Index - ${data}`}
;
+ },
+ },
+ ],
+ },
+ ],
+ {
+ hydrationData: {
+ loaderData: {
+ root: "HYDRATED ROOT",
+ },
+ },
+ future: {
+ v7_partialHydration: true,
+ },
+ }
+ );
+ let { container } = render();
+
+ expect(getHtml(container)).toMatchInlineSnapshot(`""`);
+
+ // We can't assert this in all 3 test executions because we use `warningOnce`
+ // internally to avoid logging on every render
+ if (!didAssertMissingHydrateFallback) {
+ didAssertMissingHydrateFallback = true;
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(consoleWarn).toHaveBeenCalledWith(
+ "No `HydrateFallback` element provided to render during initial hydration"
+ );
+ }
+
+ dfd.resolve("INDEX DATA");
+ await waitFor(() => screen.getByText(/INDEX DATA/));
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index - INDEX DATA
+
+ "
+ `);
+ });
+
+ it("deprecates fallbackElement", async () => {
+ let dfd1 = createDeferred();
+ let dfd2 = createDeferred();
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => dfd1.promise,
+ HydrateFallback: () => Root Loading...
,
+ Component() {
+ let data = useLoaderData() as string;
+ return (
+ <>
+ {`Home - ${data}`}
+
+ >
+ );
+ },
+ children: [
+ {
+ id: "index",
+ index: true,
+ loader: () => dfd2.promise,
+ Component() {
+ let data = useLoaderData() as string;
+ return {`Index - ${data}`}
;
+ },
+ },
+ ],
+ },
+ ],
+ {
+ hydrationData: {
+ loaderData: {
+ root: "HYDRATED ROOT",
+ },
+ },
+ future: {
+ v7_partialHydration: true,
+ },
+ }
+ );
+ let { container } = render(
+ fallbackElement...}
+ />
+ );
+
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ ""
+ `);
+
+ expect(consoleWarn).toHaveBeenCalledWith(
+ "`` is deprecated when using " +
+ "`v7_partialHydration`, use a `HydrateFallback` component instead"
+ );
+
+ dfd1.resolve("ROOT DATA");
+ dfd2.resolve("INDEX DATA");
+ await waitFor(() => screen.getByText(/INDEX DATA/));
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index - INDEX DATA
+
+ "
+ `);
+ });
+
+ it("does not re-run loaders that don't have loader data due to errors", async () => {
+ let spy = jest.fn();
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => "ROOT",
+ Component() {
+ let data = useLoaderData() as string;
+ return (
+ <>
+ {`Home - ${data}`}
+
+ >
+ );
+ },
+ children: [
+ {
+ id: "index",
+ index: true,
+ loader: spy,
+ HydrateFallback: () => Index Loading...
,
+ Component() {
+ let data = useLoaderData() as string;
+ return {`Index - ${data}`}
;
+ },
+ ErrorBoundary() {
+ let error = useRouteError() as string;
+ return {error}
;
+ },
+ },
+ ],
+ },
+ ],
+ {
+ hydrationData: {
+ loaderData: {
+ root: "HYDRATED ROOT",
+ },
+ errors: {
+ index: "INDEX ERROR",
+ },
+ },
+ future: {
+ v7_partialHydration: true,
+ },
+ }
+ );
+ let { container } = render();
+
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ INDEX ERROR
+
+
"
+ `);
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it("lets users force hydration loader execution with loader.hydrate=true", async () => {
+ let dfd = createDeferred();
+ let indexLoader: LoaderFunction = () => dfd.promise;
+ indexLoader.hydrate = true;
+ let router = createTestRouter(
+ [
+ {
+ id: "root",
+ path: "/",
+ loader: () => "ROOT",
+ 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",
+ index: "INDEX INITIAL",
+ },
+ },
+ future: {
+ v7_partialHydration: true,
+ },
+ }
+ );
+ let { container } = render();
+
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index - INDEX INITIAL
+
+ "
+ `);
+
+ dfd.resolve("INDEX UPDATED");
+ await waitFor(() => screen.getByText(/INDEX UPDATED/));
+ expect(getHtml(container)).toMatchInlineSnapshot(`
+ "
+
+ Home - HYDRATED ROOT
+
+
+ Index - INDEX UPDATED
+
+ "
+ `);
+ });
+}
diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx
index 49b707daae..54f37a1121 100644
--- a/packages/react-router-dom/index.tsx
+++ b/packages/react-router-dom/index.tsx
@@ -634,7 +634,8 @@ export function RouterProvider({
React.useEffect(() => {
warning(
fallbackElement == null || !router.future.v7_partialHydration,
- "`` is deprecated when using `v7_partialHydration`"
+ "`` is deprecated when using " +
+ "`v7_partialHydration`, use a `HydrateFallback` component instead"
);
// Only log this once on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -692,7 +693,7 @@ export function RouterProvider({
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}}
>
- {state.initialized ? (
+ {state.initialized || router.future.v7_partialHydration ? (
{
warning(
fallbackElement == null || !router.future.v7_partialHydration,
- "`` is deprecated when using `v7_partialHydration`"
+ "`` is deprecated when using " +
+ "`v7_partialHydration`, use a `HydrateFallback` component instead"
);
// Only log this once on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -172,7 +173,7 @@ export function RouterProvider({
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}}
>
- {state.initialized ? (
+ {state.initialized || router.future.v7_partialHydration ? (
{
});
describe("when set to true", () => {
- it("starts with initialized=true, runs unhydrated loaders with partial hydrationData", async () => {
+ it("starts with initialized=false, runs unhydrated loaders with partial hydrationData", async () => {
let spy = jest.fn();
let shouldRevalidateSpy = jest.fn((args) => args.defaultShouldRevalidate);
let dfd = createDeferred();
@@ -291,7 +291,7 @@ describe("future.v7_partialHydration", () => {
historyAction: "POP",
location: { pathname: "/" },
loaderData: { root: "LOADER DATA" },
- initialized: true,
+ initialized: false,
navigation: { state: "idle" },
});
@@ -322,7 +322,7 @@ describe("future.v7_partialHydration", () => {
});
});
- it("starts with initialized=true, runs hydrated loaders when loader.hydrate=true", async () => {
+ it("starts with initialized=false, runs hydrated loaders when loader.hydrate=true", async () => {
let spy = jest.fn();
let shouldRevalidateSpy = jest.fn((args) => args.defaultShouldRevalidate);
let dfd = createDeferred();
@@ -367,7 +367,7 @@ describe("future.v7_partialHydration", () => {
root: "LOADER DATA",
index: "INDEX INITIAL",
},
- initialized: true,
+ initialized: false,
navigation: { state: "idle" },
});
diff --git a/packages/router/router.ts b/packages/router/router.ts
index 5992912a11..8fefa26d9b 100644
--- a/packages/router/router.ts
+++ b/packages/router/router.ts
@@ -817,19 +817,34 @@ export function createRouter(init: RouterInit): Router {
initialErrors = { [route.id]: error };
}
- // "Initialized" here really means "Can `RouterProvider` render my route tree?"
- // Prior to `route.HydrateFallback`, we only had a root `fallbackElement` so we used
- // `state.initialized` to render that instead of ``. Now that we
- // support route level fallbacks we can always render and we'll just render
- // as deep as we have data for and detect the nearest ancestor HydrateFallback
- let initialized =
- future.v7_partialHydration ||
+ let initialized: boolean;
+ let hasLazyRoutes = initialMatches.some((m) => m.route.lazy);
+ let hasLoaders = initialMatches.some((m) => m.route.loader);
+ if (hasLazyRoutes) {
// All initialMatches need to be loaded before we're ready. If we have lazy
// functions around still then we'll need to run them in initialize()
- (!initialMatches.some((m) => m.route.lazy) &&
- // And we have to either have no loaders or have been provided hydrationData
- (!initialMatches.some((m) => m.route.loader) ||
- init.hydrationData != null));
+ initialized = false;
+ } else if (!hasLoaders) {
+ // If we've got no loaders to run, then we're good to go
+ initialized = true;
+ } else if (future.v7_partialHydration) {
+ // If partial hydration is enabled, we're initialized so long as we were
+ // provided with hydrationData for every route with a loader, and no loaders
+ // were marked for explicit hydration
+ let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
+ let errors = init.hydrationData ? init.hydrationData.errors : null;
+ initialized = initialMatches.every(
+ (m) =>
+ m.route.loader &&
+ m.route.loader.hydrate !== true &&
+ ((loaderData && loaderData[m.route.id] !== undefined) ||
+ (errors && errors[m.route.id] !== undefined))
+ );
+ } else {
+ // Without partial hydration - we're initialized if we were provided any
+ // hydrationData - which is expected to be complete
+ initialized = init.hydrationData != null;
+ }
let router: Router;
let state: RouterState = {
@@ -1010,11 +1025,7 @@ export function createRouter(init: RouterInit): Router {
// in the normal navigation flow. For SSR it's expected that lazy modules are
// resolved prior to router creation since we can't go into a fallbackElement
// UI for SSR'd apps
- if (
- !state.initialized ||
- (future.v7_partialHydration &&
- state.matches.some((m) => isUnhydratedRoute(state, m.route)))
- ) {
+ if (!state.initialized) {
startNavigation(HistoryAction.Pop, state.location, {
initialHydration: true,
});
From 3f31fb331d6ca13e7ad97d705542ec2216e7a94c Mon Sep 17 00:00:00 2001
From: Matt Brophy
Date: Wed, 6 Dec 2023 10:35:37 -0500
Subject: [PATCH 2/3] Add changeset
---
.changeset/serious-rivers-rest.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 .changeset/serious-rivers-rest.md
diff --git a/.changeset/serious-rivers-rest.md b/.changeset/serious-rivers-rest.md
new file mode 100644
index 0000000000..d72f2ac912
--- /dev/null
+++ b/.changeset/serious-rivers-rest.md
@@ -0,0 +1,7 @@
+---
+"react-router-dom": patch
+"react-router": patch
+"@remix-run/router": patch
+---
+
+[REMOVE] Refactor internals for partial hydration
From 2b34fd034ab528deea7c6a7e2a9742a3b9d7aaf0 Mon Sep 17 00:00:00 2001
From: Matt Brophy
Date: Wed, 6 Dec 2023 10:39:28 -0500
Subject: [PATCH 3/3] Bump bundle
---
package.json | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index add36b5a32..a87b49acfe 100644
--- a/package.json
+++ b/package.json
@@ -110,19 +110,19 @@
},
"filesize": {
"packages/router/dist/router.umd.min.js": {
- "none": "50.1 kB"
+ "none": "50.4 kB"
},
"packages/react-router/dist/react-router.production.min.js": {
- "none": "14.6 kB"
+ "none": "14.7 kB"
},
"packages/react-router/dist/umd/react-router.production.min.js": {
- "none": "17.0 kB"
+ "none": "17.1 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
- "none": "16.8 kB"
+ "none": "16.9 kB"
},
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
- "none": "23.0 kB"
+ "none": "23.1 kB"
}
}
}