Skip to content

Commit 432e3c2

Browse files
committed
Extract reusable getHydrationData utility
1 parent 36b05eb commit 432e3c2

File tree

4 files changed

+97
-52
lines changed

4 files changed

+97
-52
lines changed

packages/react-router/lib/dom-export/hydrated-router.tsx

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
UNSAFE_createClientRoutesWithHMRRevalidationOptOut as createClientRoutesWithHMRRevalidationOptOut,
2626
matchRoutes,
2727
} from "react-router";
28+
import { getHydrationData } from "../dom/ssr/hydration";
2829
import { RouterProvider } from "./dom-router-provider";
2930

3031
type SSRInfo = {
@@ -126,9 +127,9 @@ function createHydratedRouter({
126127
);
127128

128129
let hydrationData: HydrationState | undefined = undefined;
129-
let loaderData = ssrInfo.context.state.loaderData;
130130
// In SPA mode we only hydrate build-time root loader data
131131
if (ssrInfo.context.isSpaMode) {
132+
let { loaderData } = ssrInfo.context.state;
132133
if (
133134
ssrInfo.manifest.routes.root?.hasLoader &&
134135
loaderData &&
@@ -141,51 +142,19 @@ function createHydratedRouter({
141142
};
142143
}
143144
} else {
144-
// Create a shallow clone of `loaderData` we can mutate for partial hydration.
145-
// When a route exports a `clientLoader` and a `HydrateFallback`, the SSR will
146-
// render the fallback so we need the client to do the same for hydration.
147-
// The server loader data has already been exposed to these route `clientLoader`'s
148-
// in `createClientRoutes` above, so we need to clear out the version we pass to
149-
// `createBrowserRouter` so it initializes and runs the client loaders.
150-
hydrationData = {
151-
...ssrInfo.context.state,
152-
loaderData: { ...loaderData },
153-
};
154-
let initialMatches = matchRoutes(
145+
hydrationData = getHydrationData(
146+
ssrInfo.context.state,
155147
routes,
148+
(routeId) => ({
149+
clientLoader: ssrInfo!.routeModules[routeId]?.clientLoader,
150+
hasLoader: ssrInfo!.manifest.routes[routeId]?.hasLoader === true,
151+
hasHydrateFallback:
152+
ssrInfo!.routeModules[routeId]?.HydrateFallback != null,
153+
}),
156154
window.location,
157-
window.__reactRouterContext?.basename
155+
window.__reactRouterContext?.basename,
156+
ssrInfo.context.isSpaMode
158157
);
159-
if (initialMatches) {
160-
for (let match of initialMatches) {
161-
let routeId = match.route.id;
162-
let route = ssrInfo.routeModules[routeId];
163-
let manifestRoute = ssrInfo.manifest.routes[routeId];
164-
// Clear out the loaderData to avoid rendering the route component when the
165-
// route opted into clientLoader hydration and either:
166-
// * gave us a HydrateFallback
167-
// * or doesn't have a server loader and we have no data to render
168-
if (
169-
route &&
170-
manifestRoute &&
171-
shouldHydrateRouteLoader(
172-
manifestRoute,
173-
route,
174-
ssrInfo.context.isSpaMode
175-
) &&
176-
(route.HydrateFallback || !manifestRoute.hasLoader)
177-
) {
178-
delete hydrationData.loaderData![routeId];
179-
} else if (manifestRoute && !manifestRoute.hasLoader) {
180-
// Since every Remix route gets a `loader` on the client side to load
181-
// the route JS module, we need to add a `null` value to `loaderData`
182-
// for any routes that don't have server loaders so our partial
183-
// hydration logic doesn't kick off the route module loaders during
184-
// hydration
185-
hydrationData.loaderData![routeId] = null;
186-
}
187-
}
188-
}
189158

190159
if (hydrationData && hydrationData.errors) {
191160
// TODO: De-dup this or remove entirely in v7 where single fetch is the
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { DataRouteObject } from "../../context";
2+
import type { Path } from "../../router/history";
3+
import type { Router as DataRouter, HydrationState } from "../../router/router";
4+
import { matchRoutes } from "../../router/utils";
5+
import type { ClientLoaderFunction } from "./routeModules";
6+
import { shouldHydrateRouteLoader } from "./routes";
7+
8+
export function getHydrationData(
9+
state: {
10+
loaderData?: DataRouter["state"]["loaderData"];
11+
actionData?: DataRouter["state"]["actionData"];
12+
errors?: DataRouter["state"]["errors"];
13+
},
14+
routes: DataRouteObject[],
15+
getRouteInfo: (routeId: string) => {
16+
clientLoader: ClientLoaderFunction | undefined;
17+
hasLoader: boolean;
18+
hasHydrateFallback: boolean;
19+
},
20+
location: Path,
21+
basename: string | undefined,
22+
isSpaMode: boolean
23+
): HydrationState {
24+
// Create a shallow clone of `loaderData` we can mutate for partial hydration.
25+
// When a route exports a `clientLoader` and a `HydrateFallback`, the SSR will
26+
// render the fallback so we need the client to do the same for hydration.
27+
// The server loader data has already been exposed to these route `clientLoader`'s
28+
// in `createClientRoutes` above, so we need to clear out the version we pass to
29+
// `createBrowserRouter` so it initializes and runs the client loaders.
30+
let hydrationData = {
31+
...state,
32+
loaderData: { ...state.loaderData },
33+
};
34+
let initialMatches = matchRoutes(routes, location, basename);
35+
if (initialMatches) {
36+
for (let match of initialMatches) {
37+
let routeId = match.route.id;
38+
let routeInfo = getRouteInfo(routeId);
39+
// Clear out the loaderData to avoid rendering the route component when the
40+
// route opted into clientLoader hydration and either:
41+
// * gave us a HydrateFallback
42+
// * or doesn't have a server loader and we have no data to render
43+
if (
44+
shouldHydrateRouteLoader(
45+
routeId,
46+
routeInfo.clientLoader,
47+
routeInfo.hasLoader,
48+
isSpaMode
49+
) &&
50+
(routeInfo.hasHydrateFallback || !routeInfo.hasLoader)
51+
) {
52+
delete hydrationData.loaderData![routeId];
53+
} else if (!routeInfo.hasLoader) {
54+
// Since every Remix route gets a `loader` on the client side to load
55+
// the route JS module, we need to add a `null` value to `loaderData`
56+
// for any routes that don't have server loaders so our partial
57+
// hydration logic doesn't kick off the route module loaders during
58+
// hydration
59+
hydrationData.loaderData![routeId] = null;
60+
}
61+
}
62+
}
63+
64+
return hydrationData;
65+
}

packages/react-router/lib/dom/ssr/routes.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import type {
99
ShouldRevalidateFunctionArgs,
1010
} from "../../router/utils";
1111
import { ErrorResponseImpl, compilePath } from "../../router/utils";
12-
import type { RouteModule, RouteModules } from "./routeModules";
12+
import type {
13+
ClientLoaderFunction,
14+
RouteModule,
15+
RouteModules,
16+
} from "./routeModules";
1317
import { loadRouteModule } from "./routeModules";
1418
import type { FutureConfig } from "./entry";
1519
import { prefetchRouteCss, prefetchStyleLinks } from "./links";
@@ -382,8 +386,9 @@ export function createClientRoutes(
382386

383387
// Let React Router know whether to run this on hydration
384388
dataRoute.loader.hydrate = shouldHydrateRouteLoader(
385-
route,
386-
routeModule,
389+
route.id,
390+
routeModule.clientLoader,
391+
route.hasLoader,
387392
isSpaMode
388393
);
389394

@@ -676,13 +681,14 @@ function getRouteModuleComponent(routeModule: RouteModule) {
676681
}
677682

678683
export function shouldHydrateRouteLoader(
679-
route: EntryRoute,
680-
routeModule: RouteModule,
684+
routeId: string,
685+
clientLoader: ClientLoaderFunction | undefined,
686+
hasLoader: boolean,
681687
isSpaMode: boolean
682688
) {
683689
return (
684-
(isSpaMode && route.id !== "root") ||
685-
(routeModule.clientLoader != null &&
686-
(routeModule.clientLoader.hydrate === true || route.hasLoader !== true))
690+
(isSpaMode && routeId !== "root") ||
691+
(clientLoader != null &&
692+
(clientLoader.hydrate === true || hasLoader !== true))
687693
);
688694
}

packages/react-router/lib/dom/ssr/server.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ export function ServerRouter({
5757
if (
5858
route &&
5959
manifestRoute &&
60-
shouldHydrateRouteLoader(manifestRoute, route, context.isSpaMode) &&
60+
shouldHydrateRouteLoader(
61+
routeId,
62+
route.clientLoader,
63+
manifestRoute.hasLoader,
64+
context.isSpaMode
65+
) &&
6166
(route.HydrateFallback || !manifestRoute.hasLoader)
6267
) {
6368
delete context.staticHandlerContext.loaderData[routeId];

0 commit comments

Comments
 (0)