diff --git a/packages/astro/src/actions/runtime/middleware.ts b/packages/astro/src/actions/runtime/middleware.ts index 2fca9c5459..1425cadc84 100644 --- a/packages/astro/src/actions/runtime/middleware.ts +++ b/packages/astro/src/actions/runtime/middleware.ts @@ -10,7 +10,6 @@ import { type SerializedActionResult, serializeActionResult, } from './virtual/shared.js'; -import type { AstroCookie } from '../../core/cookies/cookies.js'; export type Locals = { _actionsInternal: { @@ -30,7 +29,7 @@ export const onRequest = defineMiddleware(async (context, next) => { const actionResultCookie = context.cookies.get('_actionResult'); if (actionResultCookie) { - return renderResult({ context, next, actionResultCookie }); + return renderResult({ context, next, ...actionResultCookie.json() }); } // Heuristic: If body is null, Astro might've reset this for prerendering. @@ -59,11 +58,17 @@ export const onRequest = defineMiddleware(async (context, next) => { async function renderResult({ context, next, - actionResultCookie, -}: { context: APIContext; next: MiddlewareNext; actionResultCookie: AstroCookie }) { + actionResult, + actionName, +}: { + context: APIContext; + next: MiddlewareNext; + actionResult: SerializedActionResult; + actionName: string; +}) { const locals = context.locals as Locals; - locals._actionsInternal = actionResultCookie.json(); + locals._actionsInternal = { actionResult, actionName }; const response = await next(); context.cookies.delete('_actionResult'); @@ -100,6 +105,15 @@ async function handlePost({ const action = baseAction.bind(context); const actionResult = await action(formData); + if (context.url.searchParams.get('_actionResultBehavior') === 'none') { + return renderResult({ + context, + next, + actionName, + actionResult: serializeActionResult(actionResult), + }); + } + return redirectWithResult({ context, next, actionName, actionResult }); } diff --git a/packages/astro/src/core/cookies/cookies.ts b/packages/astro/src/core/cookies/cookies.ts index 41acbac41f..852be913d6 100644 --- a/packages/astro/src/core/cookies/cookies.ts +++ b/packages/astro/src/core/cookies/cookies.ts @@ -35,7 +35,7 @@ const DELETED_EXPIRATION = new Date(0); const DELETED_VALUE = 'deleted'; const responseSentSymbol = Symbol.for('astro.responseSent'); -export class AstroCookie implements AstroCookieInterface { +class AstroCookie implements AstroCookieInterface { constructor(public value: string) {} json() { if (this.value === undefined) { diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index f38ba3fa96..4a692b525b 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -16,13 +16,18 @@ function toActionProxy(actionCallback = {}, aggregatedPath = '') { toString: () => action.queryString, // Progressive enhancement info for React. $$FORM_ACTION: function () { + const searchParams = new URLSearchParams(action.toString()); + // Astro will redirect with a GET request by default. + // Disable this behavior to preserve form state + // for React's progressive enhancement. + searchParams.set('_actionResultBehavior', 'none'); return { method: 'POST', // `name` creates a hidden input. // It's unused by Astro, but we can't turn this off. // At least use a name that won't conflict with a user's formData. name: '_astroAction', - action: action.toString(), + action: '?' + searchParams.toString(), }; }, // Note: `orThrow` does not have progressive enhancement info.