Skip to content

Commit f294df8

Browse files
authored
Fix defaultShouldRevalidate when using single fetch (Remix PR 10139) (#12170)
1 parent a6e57f4 commit f294df8

File tree

3 files changed

+166
-64
lines changed

3 files changed

+166
-64
lines changed

integration/single-fetch-test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,6 +2108,98 @@ test.describe("single-fetch", () => {
21082108
).toBe(true);
21092109
});
21102110

2111+
test("provides the proper defaultShouldRevalidate value", async ({
2112+
page,
2113+
}) => {
2114+
let fixture = await createFixture({
2115+
files: {
2116+
...files,
2117+
"app/routes/_index.tsx": js`
2118+
import { Link } from 'react-router';
2119+
export default function Component() {
2120+
return <Link to="/parent/a">Go to /parent/a</Link>;
2121+
}
2122+
`,
2123+
"app/routes/parent.tsx": js`
2124+
import { Link, Outlet, useLoaderData } from 'react-router';
2125+
let count = 0;
2126+
export function loader({ request }) {
2127+
return { count: ++count };
2128+
}
2129+
export function shouldRevalidate({ defaultShouldRevalidate }) {
2130+
return defaultShouldRevalidate;
2131+
}
2132+
export default function Component() {
2133+
return (
2134+
<>
2135+
<p id="parent">Parent Count: {useLoaderData().count}</p>
2136+
<Link to="/parent/a">Go to /parent/a</Link>
2137+
<Link to="/parent/b">Go to /parent/b</Link>
2138+
<Outlet/>
2139+
</>
2140+
);
2141+
}
2142+
`,
2143+
"app/routes/parent.a.tsx": js`
2144+
import { useLoaderData } from 'react-router';
2145+
let count = 0;
2146+
export function loader({ request }) {
2147+
return { count: ++count };
2148+
}
2149+
export default function Component() {
2150+
return <p id="a">A Count: {useLoaderData().count}</p>;
2151+
}
2152+
`,
2153+
"app/routes/parent.b.tsx": js`
2154+
import { useLoaderData } from 'react-router';
2155+
let count = 0;
2156+
export function loader({ request }) {
2157+
return { count: ++count };
2158+
}
2159+
export default function Component() {
2160+
return <p id="b">B Count: {useLoaderData().count}</p>;
2161+
}
2162+
`,
2163+
},
2164+
});
2165+
let appFixture = await createAppFixture(fixture);
2166+
let app = new PlaywrightFixture(appFixture, page);
2167+
2168+
let urls: string[] = [];
2169+
page.on("request", (req) => {
2170+
if (req.url().includes(".data")) {
2171+
urls.push(req.url());
2172+
}
2173+
});
2174+
2175+
await app.goto("/");
2176+
2177+
await app.clickLink("/parent/a");
2178+
await page.waitForSelector("#a");
2179+
expect(await app.getHtml("#parent")).toContain("Parent Count: 1");
2180+
expect(await app.getHtml("#a")).toContain("A Count: 1");
2181+
expect(urls.length).toBe(1);
2182+
expect(urls[0].endsWith("/parent/a.data")).toBe(true);
2183+
urls = [];
2184+
2185+
await app.clickLink("/parent/b");
2186+
await page.waitForSelector("#b");
2187+
expect(await app.getHtml("#parent")).toContain("Parent Count: 2");
2188+
expect(await app.getHtml("#b")).toContain("B Count: 1");
2189+
expect(urls.length).toBe(1);
2190+
// Reload the parent route
2191+
expect(urls[0].endsWith("/parent/b.data")).toBe(true);
2192+
urls = [];
2193+
2194+
await app.clickLink("/parent/a");
2195+
await page.waitForSelector("#a");
2196+
expect(await app.getHtml("#parent")).toContain("Parent Count: 3");
2197+
expect(await app.getHtml("#a")).toContain("A Count: 2");
2198+
expect(urls.length).toBe(1);
2199+
// Reload the parent route
2200+
expect(urls[0].endsWith("/parent/a.data")).toBe(true);
2201+
});
2202+
21112203
test("does not add a _routes param for routes without loaders", async ({
21122204
page,
21132205
}) => {

packages/react-router/lib/dom/ssr/links.ts

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ export function getNewMatchesForLinks(
168168
location: Location,
169169
mode: "data" | "assets"
170170
): AgnosticDataRouteMatch[] {
171-
let path = parsePathPatch(page);
172-
173171
let isNew = (match: AgnosticDataRouteMatch, index: number) => {
174172
if (!currentMatches[index]) return true;
175173
return match.route.id !== currentMatches[index].route.id;
@@ -186,48 +184,47 @@ export function getNewMatchesForLinks(
186184
);
187185
};
188186

189-
// NOTE: keep this mostly up-to-date w/ the transition data diff, but this
187+
if (mode === "assets") {
188+
return nextMatches.filter(
189+
(match, index) => isNew(match, index) || matchPathChanged(match, index)
190+
);
191+
}
192+
193+
// NOTE: keep this mostly up-to-date w/ the router data diff, but this
190194
// version doesn't care about submissions
191-
let newMatches =
192-
mode === "data" && location.search !== path.search
193-
? // this is really similar to stuff in transition.ts, maybe somebody smarter
194-
// than me (or in less of a hurry) can share some of it. You're the best.
195-
nextMatches.filter((match, index) => {
196-
let manifestRoute = manifest.routes[match.route.id];
197-
if (!manifestRoute.hasLoader) {
198-
return false;
199-
}
200-
201-
if (isNew(match, index) || matchPathChanged(match, index)) {
202-
return true;
203-
}
204-
205-
if (match.route.shouldRevalidate) {
206-
let routeChoice = match.route.shouldRevalidate({
207-
currentUrl: new URL(
208-
location.pathname + location.search + location.hash,
209-
window.origin
210-
),
211-
currentParams: currentMatches[0]?.params || {},
212-
nextUrl: new URL(page, window.origin),
213-
nextParams: match.params,
214-
defaultShouldRevalidate: true,
215-
});
216-
if (typeof routeChoice === "boolean") {
217-
return routeChoice;
218-
}
219-
}
220-
return true;
221-
})
222-
: nextMatches.filter((match, index) => {
223-
let manifestRoute = manifest.routes[match.route.id];
224-
return (
225-
(mode === "assets" || manifestRoute.hasLoader) &&
226-
(isNew(match, index) || matchPathChanged(match, index))
227-
);
195+
// TODO: this is really similar to stuff in router.ts, maybe somebody smarter
196+
// than me (or in less of a hurry) can share some of it. You're the best.
197+
if (mode === "data") {
198+
return nextMatches.filter((match, index) => {
199+
let manifestRoute = manifest.routes[match.route.id];
200+
if (!manifestRoute.hasLoader) {
201+
return false;
202+
}
203+
204+
if (isNew(match, index) || matchPathChanged(match, index)) {
205+
return true;
206+
}
207+
208+
if (match.route.shouldRevalidate) {
209+
let routeChoice = match.route.shouldRevalidate({
210+
currentUrl: new URL(
211+
location.pathname + location.search + location.hash,
212+
window.origin
213+
),
214+
currentParams: currentMatches[0]?.params || {},
215+
nextUrl: new URL(page, window.origin),
216+
nextParams: match.params,
217+
defaultShouldRevalidate: true,
228218
});
219+
if (typeof routeChoice === "boolean") {
220+
return routeChoice;
221+
}
222+
}
223+
return true;
224+
});
225+
}
229226

230-
return newMatches;
227+
return [];
231228
}
232229

233230
export function getModuleLinkHrefs(
@@ -320,13 +317,6 @@ function dedupeLinkDescriptors<Descriptor extends LinkDescriptor>(
320317
}, [] as KeyedLinkDescriptor<Descriptor>[]);
321318
}
322319

323-
// https://github.com/remix-run/history/issues/897
324-
function parsePathPatch(href: string) {
325-
let path = parsePath(href);
326-
if (path.search === undefined) path.search = "";
327-
return path;
328-
}
329-
330320
// Detect if this browser supports <link rel="preload"> (or has it enabled).
331321
// Originally added to handle the firefox `network.preload` config:
332322
// https://bugzilla.mozilla.org/show_bug.cgi?id=1847811

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

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
ActionFunctionArgs,
66
LoaderFunctionArgs,
77
ShouldRevalidateFunction,
8+
ShouldRevalidateFunctionArgs,
89
} from "../../router/utils";
910
import { ErrorResponseImpl } from "../../router/utils";
1011
import type { RouteModule, RouteModules } from "./routeModules";
@@ -288,13 +289,11 @@ export function createClientRoutes(
288289
...dataRoute,
289290
...getRouteComponents(route, routeModule, isSpaMode),
290291
handle: routeModule.handle,
291-
shouldRevalidate: needsRevalidation
292-
? wrapShouldRevalidateForHdr(
293-
route.id,
294-
routeModule.shouldRevalidate,
295-
needsRevalidation
296-
)
297-
: routeModule.shouldRevalidate,
292+
shouldRevalidate: getShouldRevalidateFunction(
293+
routeModule,
294+
route.id,
295+
needsRevalidation
296+
),
298297
});
299298

300299
let hasInitialData =
@@ -456,19 +455,15 @@ export function createClientRoutes(
456455
});
457456
}
458457

459-
if (needsRevalidation) {
460-
lazyRoute.shouldRevalidate = wrapShouldRevalidateForHdr(
461-
route.id,
462-
mod.shouldRevalidate,
463-
needsRevalidation
464-
);
465-
}
466-
467458
return {
468459
...(lazyRoute.loader ? { loader: lazyRoute.loader } : {}),
469460
...(lazyRoute.action ? { action: lazyRoute.action } : {}),
470461
hasErrorBoundary: lazyRoute.hasErrorBoundary,
471-
shouldRevalidate: lazyRoute.shouldRevalidate,
462+
shouldRevalidate: getShouldRevalidateFunction(
463+
lazyRoute,
464+
route.id,
465+
needsRevalidation
466+
),
472467
handle: lazyRoute.handle,
473468
// No need to wrap these in layout since the root route is never
474469
// loaded via route.lazy()
@@ -492,6 +487,31 @@ export function createClientRoutes(
492487
});
493488
}
494489

490+
function getShouldRevalidateFunction(
491+
route: Partial<DataRouteObject>,
492+
routeId: string,
493+
needsRevalidation: Set<string> | undefined
494+
) {
495+
// During HDR we force revalidation for updated routes
496+
if (needsRevalidation) {
497+
return wrapShouldRevalidateForHdr(
498+
routeId,
499+
route.shouldRevalidate,
500+
needsRevalidation
501+
);
502+
}
503+
504+
// Single fetch revalidates by default, so override the RR default value which
505+
// matches the multi-fetch behavior with `true`
506+
if (route.shouldRevalidate) {
507+
let fn = route.shouldRevalidate;
508+
return (opts: ShouldRevalidateFunctionArgs) =>
509+
fn({ ...opts, defaultShouldRevalidate: true });
510+
}
511+
512+
return route.shouldRevalidate;
513+
}
514+
495515
// When an HMR / HDR update happens we opt out of all user-defined
496516
// revalidation logic and force a revalidation on the first call
497517
function wrapShouldRevalidateForHdr(

0 commit comments

Comments
 (0)