Skip to content

Commit 336ef85

Browse files
committed
Revert "Refactor dataStrategy for easier RSC abstraction"
This reverts commit 65920e8.
1 parent 65920e8 commit 336ef85

File tree

2 files changed

+98
-85
lines changed

2 files changed

+98
-85
lines changed

integration/single-fetch-test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { test, expect } from "@playwright/test";
22
import {
33
UNSAFE_ServerMode as ServerMode,
44
UNSAFE_SingleFetchRedirectSymbol as SingleFetchRedirectSymbol,
5-
UNSAFE_ServerMode,
65
} from "react-router";
76

87
import {

packages/react-router/lib/dom/ssr/single-fetch.tsx

+98-84
Original file line numberDiff line numberDiff line change
@@ -316,130 +316,145 @@ async function singleFetchLoaderNavigationStrategy(
316316
matches: DataStrategyFunctionArgs["matches"],
317317
basename: string | undefined
318318
) {
319-
// Routes that need to be loaded from the server
320-
let serverLoaderRouteDfds = new Map<string, Deferred>();
319+
// Track which routes need a server load - in case we need to tack on a
320+
// `_routes` param
321+
let routesParams = new Set<string>();
321322

322-
// Did any routes opt-out of a server loader via`shouldRevalidate`/`clientLoader`?
323+
// We only add `_routes` when one or more routes opts out of a load via
324+
// `shouldRevalidate` or `clientLoader`
323325
let foundOptOutRoute = false;
324326

325-
// Deferreds to ensure all routes have loaded via match.resolve()
327+
// Deferreds for each route so we can be sure they've all loaded via
328+
// `match.resolve()`, and a singular promise that can tell us all routes
329+
// have been resolved
326330
let routeDfds = matches.map(() => createDeferred<void>());
331+
let routesLoadedPromise = Promise.all(routeDfds.map((d) => d.promise));
327332

328-
// We'll build up this results object as we loop through matches
329-
let results: Record<string, DataStrategyResult> = {};
333+
// Deferred that we'll use for the call to the server that each match can
334+
// await and parse out it's specific result
335+
let singleFetchDfd = createDeferred<SingleFetchResults>();
330336

331-
// Base URL/RequestInit for calls to the server
337+
// Base URL and RequestInit for calls to the server
332338
let url = stripIndexParam(singleFetchUrl(request.url, basename));
333339
let init = await createRequestInit(request);
334340

341+
// We'll build up this results object as we loop through matches
342+
let results: Record<string, DataStrategyResult> = {};
343+
335344
let resolvePromise = Promise.all(
336345
matches.map(async (m, i) =>
337346
m.resolve(async (handler) => {
338347
routeDfds[i].resolve();
339348

340-
let { id: routeId, shouldRevalidate } = m.route;
341-
let manifestRoute = manifest.routes[routeId];
342-
invariant(manifestRoute, "No manifest route found for dataStrategy");
343-
let { hasLoader, hasClientLoader } = manifestRoute;
344-
345-
// Short circuit if there's no loader to call. Give this route an
346-
// `undefined` result because we always have a `loader` on the client
347-
// route in framework mode (for loading module/styles) so this ensures
348-
// it doesn't think it's waiting for data and trigger `HydrateFallback`
349-
if (!hasLoader && !hasClientLoader && !window.__reactRouterHdrActive) {
350-
results[routeId] = { type: "data", result: undefined };
351-
return;
352-
}
353-
354-
// Respect `shouldLoad` on initial load because we're only dealing with
355-
// `clientLoader.hydrate` routes which will fall into the `clientLoader`
356-
// section below.
357-
if (!m.shouldLoad && !router.state.initialized) {
358-
return;
359-
}
349+
let manifestRoute = manifest.routes[m.route.id];
350+
351+
// Note: If this logic changes for routes that should not participate
352+
// in Single Fetch, make sure you update getLowestLoadingIndex above
353+
// as well
354+
if (!m.shouldLoad) {
355+
// If we're not yet initialized and this is the initial load, respect
356+
// `shouldLoad` because we're only dealing with `clientLoader.hydrate`
357+
// routes which will fall into the `clientLoader` section below.
358+
if (!router.state.initialized) {
359+
return;
360+
}
360361

361-
// Otherwise, skip the call if we currently have data and a `shouldRevalidate`
362-
// function - which implies that the user opted out via `shouldRevalidate`
363-
if (
364-
!m.shouldLoad &&
365-
routeId in router.state.loaderData &&
366-
shouldRevalidate
367-
) {
368-
// If a server loader exists, ensure it's excluded from the .data request
369-
if (hasLoader) {
370-
foundOptOutRoute = true;
362+
// Otherwise, we opt out if we currently have data and a
363+
// `shouldRevalidate` function. This implies that the user opted out
364+
// via `shouldRevalidate`
365+
if (
366+
m.route.id in router.state.loaderData &&
367+
manifestRoute &&
368+
m.route.shouldRevalidate
369+
) {
370+
if (manifestRoute.hasLoader) {
371+
// If we have a server loader, make sure we don't include it in the
372+
// single fetch .data request
373+
foundOptOutRoute = true;
374+
}
375+
return;
371376
}
372-
return;
373377
}
374378

375379
// When a route has a client loader, it opts out of the singular call and
376380
// calls it's server loader via `serverLoader()` using a `?_routes` param
377-
if (hasClientLoader) {
378-
if (hasLoader) {
381+
if (manifestRoute && manifestRoute.hasClientLoader) {
382+
if (manifestRoute.hasLoader) {
379383
foundOptOutRoute = true;
380384
}
381385
try {
382-
let result = await fetchSingleLoader(handler, url, init, routeId);
383-
results[routeId] = { type: "data", result };
386+
let result = await fetchSingleLoader(
387+
handler,
388+
url,
389+
init,
390+
m.route.id
391+
);
392+
results[m.route.id] = { type: "data", result };
384393
} catch (e) {
385-
results[routeId] = { type: "error", result: e };
394+
results[m.route.id] = { type: "error", result: e };
386395
}
387396
return;
388397
}
389398

390-
let dfd = createDeferred();
391-
serverLoaderRouteDfds.set(routeId, dfd);
392-
await handler(() => dfd.promise);
399+
// Load this route on the server if it has a loader
400+
if (manifestRoute && manifestRoute.hasLoader) {
401+
routesParams.add(m.route.id);
402+
}
403+
404+
// Lump this match in with the others on a singular promise
405+
try {
406+
let result = await handler(async () => {
407+
let data = await singleFetchDfd.promise;
408+
return unwrapSingleFetchResults(data, m.route.id);
409+
});
410+
results[m.route.id] = {
411+
type: "data",
412+
result,
413+
};
414+
} catch (e) {
415+
results[m.route.id] = {
416+
type: "error",
417+
result: e,
418+
};
419+
}
393420
})
394421
)
395422
);
396423

397424
// Wait for all routes to resolve above before we make the HTTP call
398-
await Promise.all(routeDfds.map((d) => d.promise));
425+
await routesLoadedPromise;
399426

400-
// Only make the server call if:
401-
// - We have loaders on the server that need fetching
402-
// - We're already hydrated - during initial hydration only clientLoaders can
403-
// run (via `clientLoader.hydrate`) which are handled above
427+
// We can skip the server call:
428+
// - On initial hydration - only clientLoaders can pass through via `clientLoader.hydrate`
429+
// - If there are no routes to fetch from the server
404430
//
405431
// One exception - if we are performing an HDR revalidation we have to call
406432
// the server in case a new loader has shown up that the manifest doesn't yet
407433
// know about
408434
if (
409-
(serverLoaderRouteDfds.size > 0 && router.state.initialized) ||
410-
window.__reactRouterHdrActive
435+
(!router.state.initialized || routesParams.size === 0) &&
436+
!window.__reactRouterHdrActive
411437
) {
412-
// When routes have opted out, add a `_routes` param to filter server loaders
413-
// Skipped in `ssr:false` because we expect to be loading static `.data` files
414-
if (ssr && foundOptOutRoute && serverLoaderRouteDfds.size > 0) {
415-
let routes = [...serverLoaderRouteDfds.keys()].join(",");
416-
url.searchParams.set("_routes", routes);
417-
}
418-
438+
singleFetchDfd.resolve({});
439+
} else {
419440
try {
420-
let { data } = (await fetchAndDecode(url, init)) as {
421-
data: SingleFetchResults;
422-
};
423-
for (let [routeId, dfd] of serverLoaderRouteDfds) {
424-
try {
425-
results[routeId] = {
426-
type: "data",
427-
result: unwrapSingleFetchResults(data, routeId),
428-
};
429-
dfd.resolve(results[routeId]);
430-
} catch (e) {
431-
results[routeId] = { type: "error", result: e };
432-
dfd.reject(results[routeId]);
433-
}
441+
// When one or more routes have opted out, we add a _routes param to
442+
// limit the loaders to those that have a server loader and did not
443+
// opt out
444+
if (ssr && foundOptOutRoute && routesParams.size > 0) {
445+
url.searchParams.set(
446+
"_routes",
447+
matches
448+
.filter((m) => routesParams.has(m.route.id))
449+
.map((m) => m.route.id)
450+
.join(",")
451+
);
434452
}
453+
454+
let data = await fetchAndDecode(url, init);
455+
singleFetchDfd.resolve(data.data as SingleFetchResults);
435456
} catch (e) {
436-
serverLoaderRouteDfds.forEach((dfd, routeId) => {
437-
results[routeId] = {
438-
type: "error",
439-
result: e,
440-
};
441-
dfd.reject(results[routeId]);
442-
});
457+
singleFetchDfd.reject(e as Error);
443458
}
444459
}
445460

@@ -658,18 +673,17 @@ function unwrapSingleFetchResult(result: SingleFetchResult, routeId: string) {
658673
}
659674
}
660675

661-
type Deferred = ReturnType<typeof createDeferred>;
662676
function createDeferred<T = unknown>() {
663677
let resolve: (val?: any) => Promise<void>;
664-
let reject: (error?: unknown) => Promise<void>;
678+
let reject: (error?: Error) => Promise<void>;
665679
let promise = new Promise<T>((res, rej) => {
666680
resolve = async (val: T) => {
667681
res(val);
668682
try {
669683
await promise;
670684
} catch (e) {}
671685
};
672-
reject = async (error?: unknown) => {
686+
reject = async (error?: Error) => {
673687
rej(error);
674688
try {
675689
await promise;

0 commit comments

Comments
 (0)