withastro/astro · error · AstroError
`getActionState()` could not find a state object.
Error message
`getActionState()` could not find a state object.
What it means
After the form Content-Type check passes, getActionState() looks for the _astroActionState form field that React's useActionState adds when the action is wrapped with the adapter's withState() helper. If the field is absent, the form was submitted without that wrapper, so there is no state payload to deserialize.
Solutions
- Wrap the action before passing it to useActionState: useActionState(withState(action), initialState)
- Read the state in the handler via getActionState({ request }) only in actions that are used with useActionState
- Do not call getActionState from actions that plain forms submit directly
Example fix
// before
const [state, formAction] = useActionState(myAction, null);
// after
import { withState } from '@astrojs/react/actions';
const [state, formAction] = useActionState(withState(myAction), null); Defensive patterns
Strategy: validation
Validate before calling
const fd = await request.clone().formData();
if (!fd.has('_astroActionState')) {
// Submitted without withState()/useActionState; nothing to read
return null;
}
const state = await getActionState({ request }); Type guard
async function hasActionState(request: Request): Promise<boolean> {
const fd = await request.clone().formData();
return typeof fd.get('_astroActionState') === 'string';
} Try / catch
let state: T | null = null;
try {
state = await getActionState<T>({ request });
} catch (err) {
if (String(err?.message).includes('could not find a state object')) {
state = null; // form posted without withState wrapper
} else throw err;
} Prevention
- Always wrap actions with withState() before useActionState()
- Keep a single helper that pairs withState and getActionState so they cannot drift
- Test the full form round-trip, not just the handler
When it happens
Trigger: Passing the raw action to useActionState instead of withState(action); submitting the action through a plain <form action={action}> with no useActionState; a hand-crafted POST that omits the hidden _astroActionState field.
Common situations: Adopting getActionState on an existing form flow that never used useActionState; forgetting that withState is what injects the serialized state into the payload; React 18 setups where useActionState is unavailable.
Related errors
- `getActionState()` must be called with a form request.
- [@astrojs/react] Importing `getContainerRenderer` from…
- BAD_REQUEST
- INTERNAL_SERVER_ERROR
- More than one JSX renderer is enabled. This will lead to…
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/2875fe82b4211a28.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/react/src/actions.ts:58
return callback;
}
/**
* Retrieve the state object from your action handler when using `useActionState()`.
* To ensure this state is retrievable, use the {@linkcode withState} helper.
*/
export async function getActionState<T>({ request }: { request: Request }): Promise<T> {
const contentType = request.headers.get('Content-Type');
if (!contentType || !isFormRequest(contentType)) {
throw new AstroError(
'`getActionState()` must be called with a form request.',
"Ensure your action uses the `accept: 'form'` option.",
);
}
const formData = await request.clone().formData();
const state = formData.get('_astroActionState')?.toString();
if (!state) {
throw new AstroError(
'`getActionState()` could not find a state object.',
'Ensure your action was passed to `useActionState()` with the `withState()` wrapper.',
);
}
return JSON.parse(state) as T;
}
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
function isFormRequest(contentType: string) {
// Split off parameters like charset or boundary
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
const type = contentType.split(';')[0].toLowerCase();
return formContentTypes.some((t) => type === t);
}
/**View on GitHub (pinned to 52e6c34790)