Skip to content

Commit 07ddf5f

Browse files
committed
wip: no-revalidate server actions
1 parent b143400 commit 07ddf5f

File tree

3 files changed

+51
-24
lines changed

3 files changed

+51
-24
lines changed

integration/rsc/rsc-test.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { test, expect } from "@playwright/test";
1+
import {
2+
test,
3+
expect,
4+
type Response as PlaywrightResponse,
5+
} from "@playwright/test";
26
import getPort from "get-port";
37

48
import { implementations, js, setupRscTest, validateRSCHtml } from "./utils";
@@ -1074,6 +1078,13 @@ implementations.forEach((implementation) => {
10741078

10751079
"src/routes/hydrate-fallback-props/home.tsx": js`
10761080
export { default, clientLoader, HydrateFallback } from "./home.client";
1081+
1082+
export const unstable_middleware = [
1083+
async (_, next) => {
1084+
const response = await next();
1085+
return response.headers.set("x-test", "test");
1086+
}
1087+
];
10771088
`,
10781089
"src/routes/hydrate-fallback-props/home.client.tsx": js`
10791090
"use client";
@@ -1124,6 +1135,10 @@ implementations.forEach((implementation) => {
11241135
"src/routes/no-revalidate-server-action/home.tsx": js`
11251136
import ClientHomeRoute from "./home.client";
11261137
1138+
export function loader() {
1139+
console.log("loader");
1140+
}
1141+
11271142
export default function HomeRoute() {
11281143
return <ClientHomeRoute identity={{}} />;
11291144
}
@@ -1568,21 +1583,34 @@ implementations.forEach((implementation) => {
15681583
validateRSCHtml(await page.content());
15691584
});
15701585

1571-
test("Supports server actions that disable revalidation", async ({
1586+
test.only("Supports server actions that disable revalidation", async ({
15721587
page,
15731588
}) => {
15741589
await page.goto(
15751590
`http://localhost:${port}/no-revalidate-server-action`,
15761591
{ waitUntil: "networkidle" },
15771592
);
15781593

1594+
const actionResponsePromise = new Promise<PlaywrightResponse>(
1595+
(resolve) => {
1596+
page.on("response", async (response) => {
1597+
if (!!(await response.request().headerValue("rsc-action-id"))) {
1598+
resolve(response);
1599+
}
1600+
});
1601+
},
1602+
);
1603+
15791604
await page.click("[data-submit]");
15801605
await page.waitForSelector("[data-state]");
15811606
await page.waitForSelector("[data-pending]", { state: "hidden" });
15821607
await page.waitForSelector("[data-revalidated]", { state: "hidden" });
15831608
expect(await page.locator("[data-state]").textContent()).toBe(
15841609
"no revalidate",
15851610
);
1611+
1612+
const actionResponse = await actionResponsePromise;
1613+
expect(await actionResponse.headerValue("x-test")).toBe("test");
15861614
});
15871615
});
15881616

packages/react-router/lib/router/router.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ export interface StaticHandler {
432432
requestContext?: unknown;
433433
filterMatchesToLoad?: (match: AgnosticDataRouteMatch) => boolean;
434434
skipLoaderErrorBubbling?: boolean;
435-
skipRevalidation?: boolean;
435+
skipRevalidation?: boolean | (() => boolean);
436436
dataStrategy?: DataStrategyFunction<unknown>;
437437
unstable_generateMiddlewareResponse?: (
438438
query: (r: Request) => Promise<StaticHandlerContext | Response>,
@@ -3631,7 +3631,9 @@ export function createStaticHandler(
36313631
skipLoaderErrorBubbling === true,
36323632
null,
36333633
filterMatchesToLoad || null,
3634-
skipRevalidation === true,
3634+
skipRevalidation === true ||
3635+
(typeof skipRevalidation === "function" &&
3636+
skipRevalidation() === true),
36353637
);
36363638

36373639
if (isResponse(result)) {
@@ -3729,7 +3731,8 @@ export function createStaticHandler(
37293731
skipLoaderErrorBubbling === true,
37303732
null,
37313733
filterMatchesToLoad || null,
3732-
skipRevalidation === true,
3734+
skipRevalidation === true ||
3735+
(typeof skipRevalidation === "function" && skipRevalidation() === true),
37333736
);
37343737

37353738
if (isResponse(result)) {
@@ -3912,7 +3915,7 @@ export function createStaticHandler(
39123915
skipLoaderErrorBubbling: boolean,
39133916
routeMatch: AgnosticDataRouteMatch | null,
39143917
filterMatchesToLoad: ((m: AgnosticDataRouteMatch) => boolean) | null,
3915-
skipRevalidation: boolean,
3918+
skipRevalidation: boolean | (() => boolean),
39163919
): Promise<Omit<StaticHandlerContext, "location" | "basename"> | Response> {
39173920
invariant(
39183921
request.signal,
@@ -3979,7 +3982,7 @@ export function createStaticHandler(
39793982
skipLoaderErrorBubbling: boolean,
39803983
isRouteRequest: boolean,
39813984
filterMatchesToLoad: ((m: AgnosticDataRouteMatch) => boolean) | null,
3982-
skipRevalidation: boolean,
3985+
skipRevalidation: boolean | (() => boolean),
39833986
): Promise<Omit<StaticHandlerContext, "location" | "basename"> | Response> {
39843987
let result: DataResult;
39853988

@@ -4054,7 +4057,10 @@ export function createStaticHandler(
40544057
};
40554058
}
40564059

4057-
if (skipRevalidation) {
4060+
if (
4061+
skipRevalidation === true ||
4062+
(typeof skipRevalidation === "function" && skipRevalidation() === true)
4063+
) {
40584064
if (isErrorResult(result)) {
40594065
let boundaryMatch = skipLoaderErrorBubbling
40604066
? actionMatch

packages/react-router/lib/rsc/server.rsc.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -721,14 +721,18 @@ async function generateRenderResponse(
721721
});
722722

723723
let actionResult: Promise<unknown> | undefined;
724+
let skipRevalidation = false;
724725
const ctx: ServerContext = {
725726
runningAction: false,
726727
};
727728
const result = await ServerStorage.run(ctx, () =>
728729
staticHandler.query(request, {
729730
requestContext,
730731
skipLoaderErrorBubbling: isDataRequest,
731-
skipRevalidation: isSubmission,
732+
skipRevalidation: () => {
733+
// TODO: This should opt out of loader calls but is not at the moment.
734+
return isSubmission || skipRevalidation;
735+
},
732736
...(routeIdsToLoad
733737
? { filterMatchesToLoad: (m) => routeIdsToLoad!.includes(m.route.id) }
734738
: null),
@@ -764,6 +768,7 @@ async function generateRenderResponse(
764768
);
765769
}
766770

771+
skipRevalidation = result?.revalidate === false;
767772
actionResult = result?.actionResult;
768773
formState = result?.formState;
769774
request = result?.revalidationRequest ?? request;
@@ -779,20 +784,6 @@ async function generateRenderResponse(
779784
undefined,
780785
);
781786
}
782-
783-
if (result && result.revalidate === false) {
784-
return generateResponse(
785-
{
786-
headers: new Headers(),
787-
statusCode: 200,
788-
payload: {
789-
type: "action",
790-
actionResult: Promise.resolve(result.actionResult),
791-
},
792-
},
793-
{ temporaryReferences },
794-
);
795-
}
796787
}
797788

798789
let staticContext = await query(request);
@@ -819,6 +810,7 @@ async function generateRenderResponse(
819810
isSubmission,
820811
actionResult,
821812
formState,
813+
skipRevalidation,
822814
staticContext,
823815
temporaryReferences,
824816
ctx.redirect?.headers,
@@ -910,6 +902,7 @@ async function generateStaticContextResponse(
910902
isSubmission: boolean,
911903
actionResult: Promise<unknown> | undefined,
912904
formState: unknown | undefined,
905+
skipRevalidation: boolean,
913906
staticContext: StaticHandlerContext,
914907
temporaryReferences: unknown,
915908
sideEffectRedirectHeaders: Headers | undefined,
@@ -986,7 +979,7 @@ async function generateStaticContextResponse(
986979
payload = {
987980
type: "action",
988981
actionResult,
989-
rerender: renderPayloadPromise(),
982+
rerender: skipRevalidation ? undefined : renderPayloadPromise(),
990983
};
991984
} else if (isSubmission && isDataRequest) {
992985
// Short circuit without matches on non server-action submissions since

0 commit comments

Comments
 (0)