diff --git a/integration/helpers/rsc-parcel-framework/app/entry.browser.tsx b/integration/helpers/rsc-parcel-framework/app/entry.browser.tsx index d77e113ed1..c4f85447dd 100644 --- a/integration/helpers/rsc-parcel-framework/app/entry.browser.tsx +++ b/integration/helpers/rsc-parcel-framework/app/entry.browser.tsx @@ -25,7 +25,11 @@ setServerCallback( ); createFromReadableStream(getRSCStream()).then((payload: RSCPayload) => { - React.startTransition(() => { + // @ts-expect-error - on 18 types, requires 19. + React.startTransition(async () => { + const formState = + payload.type === "render" ? await payload.formState : undefined; + hydrateRoot( document, React.createElement( @@ -36,6 +40,10 @@ createFromReadableStream(getRSCStream()).then((payload: RSCPayload) => { payload, }), ), + { + // @ts-expect-error - no types for this yet + formState, + }, ); }); }); diff --git a/integration/helpers/rsc-parcel-framework/app/entry.rsc.ts b/integration/helpers/rsc-parcel-framework/app/entry.rsc.ts index 0613878d09..5b3c47e0fd 100644 --- a/integration/helpers/rsc-parcel-framework/app/entry.rsc.ts +++ b/integration/helpers/rsc-parcel-framework/app/entry.rsc.ts @@ -3,6 +3,7 @@ import { createTemporaryReferenceSet, decodeAction, + decodeFormState, decodeReply, loadServerAction, renderToReadableStream, @@ -18,6 +19,7 @@ export function fetchServer(request: Request) { return matchRSCServerRequest({ createTemporaryReferenceSet, decodeAction, + decodeFormState, decodeReply, loadServerAction, request, diff --git a/integration/helpers/rsc-parcel-framework/app/index.ts b/integration/helpers/rsc-parcel-framework/app/index.ts index 44391bff05..4ead7ec640 100644 --- a/integration/helpers/rsc-parcel-framework/app/index.ts +++ b/integration/helpers/rsc-parcel-framework/app/index.ts @@ -16,12 +16,17 @@ export const requestHandler = async (request: Request) => { fetchServer, createFromReadableStream, async renderHTML(getPayload) { + const payload = await getPayload(); + const formState = + payload.type === "render" ? await payload.formState : undefined; + return await renderToReadableStream( React.createElement(unstable_RSCStaticRouter, { getPayload }), { bootstrapScriptContent: ( fetchServer as unknown as { bootstrapScript: string } ).bootstrapScript, + formState, }, ); }, diff --git a/integration/helpers/rsc-vite/src/entry.browser.tsx b/integration/helpers/rsc-vite/src/entry.browser.tsx index d8f9df5a33..e45f8a4137 100644 --- a/integration/helpers/rsc-vite/src/entry.browser.tsx +++ b/integration/helpers/rsc-vite/src/entry.browser.tsx @@ -23,7 +23,11 @@ setServerCallback( ); createFromReadableStream(getRSCStream()).then((payload) => { - startTransition(() => { + // @ts-expect-error - on 18 types, requires 19. + startTransition(async () => { + const formState = + payload.type === "render" ? await payload.formState : undefined; + hydrateRoot( document, @@ -33,6 +37,10 @@ createFromReadableStream(getRSCStream()).then((payload) => { getContext={getContext} /> , + { + // @ts-expect-error - no types for this yet + formState, + }, ); }); }); diff --git a/integration/helpers/rsc-vite/src/entry.rsc.tsx b/integration/helpers/rsc-vite/src/entry.rsc.tsx index 7087205214..e61290062f 100644 --- a/integration/helpers/rsc-vite/src/entry.rsc.tsx +++ b/integration/helpers/rsc-vite/src/entry.rsc.tsx @@ -1,6 +1,7 @@ import { createTemporaryReferenceSet, decodeAction, + decodeFormState, decodeReply, loadServerAction, renderToReadableStream, @@ -16,6 +17,7 @@ export async function fetchServer(request: Request) { createTemporaryReferenceSet, decodeReply, decodeAction, + decodeFormState, loadServerAction, request, requestContext, diff --git a/integration/helpers/rsc-vite/src/entry.ssr.tsx b/integration/helpers/rsc-vite/src/entry.ssr.tsx index 9301988a6d..cc7b7e9441 100644 --- a/integration/helpers/rsc-vite/src/entry.ssr.tsx +++ b/integration/helpers/rsc-vite/src/entry.ssr.tsx @@ -16,12 +16,17 @@ export default async function handler( request, fetchServer, createFromReadableStream, - renderHTML(getPayload) { + async renderHTML(getPayload) { + const payload = await getPayload(); + const formState = + payload.type === "render" ? await payload.formState : undefined; + return ReactDomServer.renderToReadableStream( , { bootstrapScriptContent, signal: request.signal, + formState, }, ); }, diff --git a/integration/rsc/rsc-nojs-test.ts b/integration/rsc/rsc-nojs-test.ts index abec983941..d25e4995bb 100644 --- a/integration/rsc/rsc-nojs-test.ts +++ b/integration/rsc/rsc-nojs-test.ts @@ -34,6 +34,10 @@ implementations.forEach((implementation) => { redirect("/?redirected=true", { headers: { "x-test": "test" } }); return "redirected"; } + + export async function incrementAction(prev) { + return prev + 1; + } `, "src/routes/home.client.tsx": js` "use client"; @@ -47,7 +51,7 @@ implementations.forEach((implementation) => { "src/routes/home.tsx": js` "use client"; import {useActionState} from "react"; - import { redirectAction } from "./home.actions"; + import { redirectAction, incrementAction } from "./home.actions"; import { Counter } from "./home.client"; export default function HomeRoute(props) { @@ -61,15 +65,36 @@ implementations.forEach((implementation) => { {state &&
{state}
} + ); } + + function TestActionState() { + const [state, action] = useActionState(incrementAction, 0); + return ( +
+ +
{state}
+
+ ); + } `, }, }); await page.goto(`http://localhost:${port}/`); + await expect( + page.locator("[data-action-state-increment-result]"), + ).toHaveText("0"); + await page.click("[data-action-state-increment-submit]"); + await expect( + page.locator("[data-action-state-increment-result]"), + ).toHaveText("1"); + const responseHeadersPromise = new Promise>( (resolve) => { page.addListener("response", (response) => { diff --git a/packages/react-router-dev/config/default-rsc-entries/entry.client.tsx b/packages/react-router-dev/config/default-rsc-entries/entry.client.tsx index 740e2fc6bb..1d85ca6e92 100644 --- a/packages/react-router-dev/config/default-rsc-entries/entry.client.tsx +++ b/packages/react-router-dev/config/default-rsc-entries/entry.client.tsx @@ -24,7 +24,11 @@ setServerCallback( ); createFromReadableStream(getRSCStream()).then((payload) => { - startTransition(() => { + // @ts-expect-error - on 18 types, requires 19. + startTransition(async () => { + const formState = + payload.type === "render" ? await payload.formState : undefined; + hydrateRoot( document, @@ -33,6 +37,10 @@ createFromReadableStream(getRSCStream()).then((payload) => { createFromReadableStream={createFromReadableStream} /> , + { + // @ts-expect-error - no types for this yet + formState, + }, ); }); }); diff --git a/packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx b/packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx index 48adce93ed..3505567c1f 100644 --- a/packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx +++ b/packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx @@ -1,6 +1,7 @@ import { createTemporaryReferenceSet, decodeAction, + decodeFormState, decodeReply, loadServerAction, renderToReadableStream, @@ -14,6 +15,7 @@ export async function fetchServer(request: Request) { createTemporaryReferenceSet, decodeReply, decodeAction, + decodeFormState, loadServerAction, request, routes, diff --git a/packages/react-router-dev/config/default-rsc-entries/entry.ssr.tsx b/packages/react-router-dev/config/default-rsc-entries/entry.ssr.tsx index 9301988a6d..cc7b7e9441 100644 --- a/packages/react-router-dev/config/default-rsc-entries/entry.ssr.tsx +++ b/packages/react-router-dev/config/default-rsc-entries/entry.ssr.tsx @@ -16,12 +16,17 @@ export default async function handler( request, fetchServer, createFromReadableStream, - renderHTML(getPayload) { + async renderHTML(getPayload) { + const payload = await getPayload(); + const formState = + payload.type === "render" ? await payload.formState : undefined; + return ReactDomServer.renderToReadableStream( , { bootstrapScriptContent, signal: request.signal, + formState, }, ); }, diff --git a/playground/rsc-parcel/src/entry.browser.tsx b/playground/rsc-parcel/src/entry.browser.tsx index deb830b12b..665b66b6c0 100644 --- a/playground/rsc-parcel/src/entry.browser.tsx +++ b/playground/rsc-parcel/src/entry.browser.tsx @@ -26,7 +26,11 @@ setServerCallback( createFromReadableStream(getRSCStream(), { assets: "manifest" }).then( (payload: RSCPayload) => { - React.startTransition(() => { + // @ts-expect-error - on 18 types, requires 19. + React.startTransition(async () => { + const formState = + payload.type === "render" ? await payload.formState : undefined; + hydrateRoot( document, @@ -35,7 +39,11 @@ createFromReadableStream(getRSCStream(), { assets: "manifest" }).then( routeDiscovery="eager" createFromReadableStream={createFromReadableStream} /> - + , + { + // @ts-expect-error - no types for this yet + formState + } ); }); } diff --git a/playground/rsc-parcel/src/entry.rsc.ts b/playground/rsc-parcel/src/entry.rsc.ts index eb5a0a288e..504c21b0f2 100644 --- a/playground/rsc-parcel/src/entry.rsc.ts +++ b/playground/rsc-parcel/src/entry.rsc.ts @@ -4,6 +4,7 @@ import { createTemporaryReferenceSet, decodeAction, decodeReply, + decodeFormState, loadServerAction, renderToReadableStream, // @ts-expect-error @@ -19,6 +20,7 @@ export function fetchServer(request: Request) { createTemporaryReferenceSet, decodeReply, decodeAction, + decodeFormState, loadServerAction, request, routes, diff --git a/playground/rsc-parcel/src/entry.ssr.tsx b/playground/rsc-parcel/src/entry.ssr.tsx index 56df53c587..00893bc870 100644 --- a/playground/rsc-parcel/src/entry.ssr.tsx +++ b/playground/rsc-parcel/src/entry.ssr.tsx @@ -22,12 +22,17 @@ app.use( fetchServer, createFromReadableStream, async renderHTML(getPayload) { + const payload = await getPayload(); + const formState = + payload.type === "render" ? await payload.formState : undefined; + return await renderHTMLToReadableStream( , { bootstrapScriptContent: ( fetchServer as unknown as { bootstrapScript: string } ).bootstrapScript, + formState, } ); }, diff --git a/playground/rsc-vite/src/entry.browser.tsx b/playground/rsc-vite/src/entry.browser.tsx index 476166a0c1..2f0fd7e387 100644 --- a/playground/rsc-vite/src/entry.browser.tsx +++ b/playground/rsc-vite/src/entry.browser.tsx @@ -22,7 +22,11 @@ setServerCallback( ); createFromReadableStream(getRSCStream()).then((payload) => { - startTransition(() => { + // @ts-expect-error - on 18 types, requires 19. + startTransition(async () => { + const formState = + payload.type === "render" ? await payload.formState : undefined; + hydrateRoot( document, @@ -30,7 +34,11 @@ createFromReadableStream(getRSCStream()).then((payload) => { payload={payload} createFromReadableStream={createFromReadableStream} /> - + , + { + // @ts-expect-error - no types for this yet + formState + } ); }); }); diff --git a/playground/rsc-vite/src/entry.rsc.tsx b/playground/rsc-vite/src/entry.rsc.tsx index 93cdedfda7..d1b35a18f9 100644 --- a/playground/rsc-vite/src/entry.rsc.tsx +++ b/playground/rsc-vite/src/entry.rsc.tsx @@ -2,6 +2,7 @@ import { createTemporaryReferenceSet, decodeAction, decodeReply, + decodeFormState, loadServerAction, renderToReadableStream, } from "@vitejs/plugin-rsc/rsc"; @@ -14,6 +15,7 @@ export async function fetchServer(request: Request) { createTemporaryReferenceSet, decodeReply, decodeAction, + decodeFormState, loadServerAction, request, // @ts-expect-error diff --git a/playground/rsc-vite/src/entry.ssr.tsx b/playground/rsc-vite/src/entry.ssr.tsx index 385310db7d..82bb2021ef 100644 --- a/playground/rsc-vite/src/entry.ssr.tsx +++ b/playground/rsc-vite/src/entry.ssr.tsx @@ -15,12 +15,17 @@ export default async function handler( request, fetchServer, createFromReadableStream, - renderHTML(getPayload) { + async renderHTML(getPayload) { + const payload = await getPayload(); + const formState = + payload.type === "render" ? await payload.formState : undefined; + return ReactDomServer.renderToReadableStream( , { bootstrapScriptContent, signal: request.signal, + formState, } ); },