Skip to content

Commit

Permalink
Lack of a server loader should imply clientLoader.hydrate=true
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Nov 30, 2023
1 parent 2b90c9e commit 01e349e
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 8 deletions.
47 changes: 45 additions & 2 deletions integration/client-data-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ test.describe("Client Data", () => {
"app/routes/parent.child.tsx": js`
import * as React from 'react';
import { defer, json } from '@remix-run/node'
import { Await, Outlet, useLoaderData } from '@remix-run/react'
import { Await, useLoaderData } from '@remix-run/react'
export function loader() {
return defer({
message: 'Child Server Loader',
Expand All @@ -339,7 +339,6 @@ test.describe("Client Data", () => {
}
export default function Component() {
let data = useLoaderData();
console.log('rendering component', data.lazy, data.lazy._tracked, data.lazy._value)
return (
<>
<p id="child-data">{data.message}</p>
Expand Down Expand Up @@ -404,6 +403,50 @@ test.describe("Client Data", () => {
html = await app.getHtml("main");
expect(html).toMatch("Child Client Loader");
});

test("clientLoader.hydrate is automatically implied when no server loader exists", async ({
page,
}) => {
appFixture = await createAppFixture(
await createFixture({
files: {
...getFiles({
parentClientLoader: false,
parentClientLoaderHydrate: false,
childClientLoader: false,
childClientLoaderHydrate: false,
}),
// Blow away parent.child.tsx with our own version without a server loader
"app/routes/parent.child.tsx": js`
import * as React from 'react';
import { useLoaderData } from '@remix-run/react';
// Even without setting hydrate=true, this should run on hydration
export async function clientLoader({ serverLoader }) {
await new Promise(r => setTimeout(r, 100));
return {
message: "Loader Data (clientLoader only)",
};
}
export function HydrateFallback() {
return <p>Child Fallback</p>
}
export default function Component() {
let data = useLoaderData();
return <p id="child-data">{data.message}</p>;
}
`,
},
})
);
let app = new PlaywrightFixture(appFixture, page);

await app.goto("/parent/child");
let html = await app.getHtml("main");
expect(html).toMatch("Child Fallback");
await page.waitForSelector("#child-data");
html = await app.getHtml("main");
expect(html).toMatch("Loader Data (clientLoader only)");
});
});

test.describe("clientLoader - lazy route module", () => {
Expand Down
12 changes: 7 additions & 5 deletions packages/remix-react/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ export function createServerRoutes(
path: route.path,
handle: routeModules[route.id].handle,
// For partial hydration rendering, we need to indicate when the route
// has a loader, but it won't ever be called during the static render, so
// just give it a no-op function so we can render down to the proper fallback
loader: route.hasLoader ? () => null : undefined,
// has a loader/clientLoader, but it won't ever be called during the static
// render, so just give it a no-op function so we can render down to the
// proper fallback
loader: route.hasLoader || route.hasClientLoader ? () => null : undefined,
// We don't need action/shouldRevalidate on these routes since they're
// for a static render
};
Expand Down Expand Up @@ -264,8 +265,9 @@ export function createClientRoutes(

// Let React Router know whether to run this on hydration
dataRoute.loader.hydrate =
routeModule.clientLoader != null &&
routeModule.clientLoader.hydrate === true;
route.hasLoader !== true ||
(routeModule.clientLoader != null &&
routeModule.clientLoader.hydrate === true);

dataRoute.action = ({ request, params }: ActionFunctionArgs) => {
return prefetchStylesAndCallHandler(async () => {
Expand Down
14 changes: 13 additions & 1 deletion packages/remix-react/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,19 @@ export function RemixServer({
for (let match of context.staticHandlerContext.matches) {
let routeId = match.route.id;
let route = routeModules[routeId];
if (route && route.clientLoader && route.HydrateFallback) {
let manifestRoute = context.manifest.routes[routeId];
if (
// This route specifically gave us a HydrateFallback
(route && route.clientLoader && route.HydrateFallback) ||
// This handles routes without a server loader but _with_ a clientLoader
// that will automatically opt-into clientLoader.hydrate=true. The
// staticHandler always puts a `null` in loaderData for non-loader routes
// for proper serialization but we need to set that back to `undefined`
// so _renderMatches will detect a required fallback at this level
(manifestRoute &&
manifestRoute.hasLoader == false &&
context.staticHandlerContext.loaderData[routeId] === null)
) {
context.staticHandlerContext.loaderData[routeId] = undefined;
}
}
Expand Down

0 comments on commit 01e349e

Please sign in to comment.