Agent skill
using-action-state
Teaches useActionState hook for managing form state with Server Actions in React 19. Use when implementing forms, handling form submissions, tracking pending states, or working with Server Functions.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/using-action-state
SKILL.md
useActionState Patterns for Forms
Server Action:
'use server';
export async function submitForm(previousState, formData) {
const email = formData.get('email');
if (!email || !email.includes('@')) {
return { error: 'Invalid email address' };
}
await saveToDatabase({ email });
return { success: true };
}
**Component:**
```javascript
'use client';
import { useActionState } from 'react';
import { submitForm } from './actions';
function ContactForm() {
const [state, formAction, isPending] = useActionState(submitForm, null);
return (
<form action={formAction}>
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{state?.error && <p className="error">{state.error}</p>}
{state?.success && <p className="success">Submitted!</p>}
</form>
);
}
```
</workflow>
<conditional-workflows>
## Decision Points
**Progressive Enhancement:** Add permalink as third argument: `useActionState(submitForm, null, '/api/submit')`. Form submits to URL before JS loads; server handles both cases.
**Validation:** Server Action receives previousState, returns error object for failures, success object when valid; component renders errors from state.
**Multi-Step Forms:** Track step in state; Server Action advances step or returns errors; component renders current step.
</conditional-workflows>
<progressive-disclosure>
## References
- **Server Actions**: `../../forms/skills/server-actions/SKILL.md`
- **Form Validation**: `../../forms/skills/form-validation/SKILL.md`
- **Progressive Enhancement**: `../../../research/react-19-comprehensive.md` (lines 715-722)
**Cross-Plugin References:**
- If customizing validation error messages, use the customizing-errors skill for error formatting with safeParse and field error flattening
Load as needed for specific patterns.
</progressive-disclosure>
<examples>
## Example 1: Validation with
Zod
```javascript
'use server';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
message: z.string().min(10).max(1000),
});
export async function contactAction(previousState, formData) {
const data = {
email: formData.get('email'),
message: formData.get('message'),
};
const result = schema.safeParse(data);
if (!result.success) {
return { errors: result.error.flatten().fieldErrors };
}
try {
await db.contacts.create({ data: result.data });
return { success: true };
} catch (error) {
return { error: 'Failed to submit contact form' };
}
}
```
```javascript
'use client';
import { useActionState } from 'react';
import { contactAction } from './actions';
export default function ContactForm() {
const [state, formAction, isPending] = useActionState(contactAction, null);
if (state?.success) {
return <p>Thank you for contacting us!</p>;
}
return (
<form action={formAction}>
<div>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" required />
{state?.errors?.email && <span className="error">{state.errors.email}</span>}
</div>
<div>
<label htmlFor="message">Message</label>
<textarea id="message" name="message" required />
{state?.errors?.message && <span className="error">{state.errors.message}</span>}
</div>
<button type="submit" disabled={isPending}>
{isPending ? 'Sending...' : 'Send Message'}
</button>
{state?.error && <p className="error">{state.error}</p>}
</form>
);
}
```
## Example 2: Multi-Step Form
```javascript
'use server';
export async function multiStepAction(previousState, formData) {
const step = previousState?.step || 1;
if (step === 1) {
const name = formData.get('name');
if (!name || name.length < 2) {
return { step: 1, error: 'Name is required' };
}
return { step: 2, data: { name } };
}
if (step === 2) {
const email = formData.get('email');
if (!email?.includes('@')) {
return { step: 2, error: 'Valid email required', data: previousState.data };
}
await db.users.create({
data: { ...previousState.data, email },
});
return { step: 3, success: true };
}
}
```
```javascript
'use client';
import { useActionState } from 'react';
import { multiStepAction } from './actions';
export default function MultiStepForm() {
const [state, formAction, isPending] = useActionState(multiStepAction, { step: 1 });
if (state.success) {
return <p>Registration complete!</p>;
}
return (
<form action={formAction}>
{state.step === 1 && (
<>
<h2>Step 1: Name</h2>
<input name="name" type="text" required />
{state.error && <p className="error">{state.error}</p>}
</>
)}
{state.step === 2 && (
<>
<h2>Step 2: Email</h2>
<p>Name: {state.data.name}</p>
<input name="email" type="email" required />
{state.error && <p className="error">{state.error}</p>}
</>
)}
<button type="submit" disabled={isPending}>
{isPending ? 'Processing...' : state.step === 2 ? 'Complete' : 'Next'}
</button>
</form>
);
}
```
</examples>
<constraints>
**MUST**: First parameter
is `previousState`, second is `formData`; return serializable values (no functions, symbols); access form values via `formData.get('fieldName')`; mark functions with `'use server'` directive.
**SHOULD**: Validate inputs server-side; return structured error objects for field errors; disable submit button on `isPending`; show loading indicators; use validation libraries (zod, yup).
**NEVER**: Trust client-side validation alone; return sensitive data in errors; mutate `previousState` directly; skip error handling for async operations; omit authentication/authorization checks.
</constraints>
<validation>
**After Implementation**: Test form submission (valid data → success, invalid → errors, check `isPending`); verify Server Action (receives `previousState` and `formData`, returns serializable objects, handles errors); check security (server validates all inputs, authentication/authorization implemented, no sensitive data exposed); test progressive enhancement (disable JS, form submits to permalink, server handles both cases).
</validation>
---
## Common Patterns
**Optimistic Updates**: Combine with `useOptimistic` for immediate UI feedback:
```javascript
const [state, formAction] = useActionState(addTodo, null);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos, (state, newTodo) => [
...state,
newTodo,
]);
```
See `../optimistic-updates/SKILL.md`.
**Reset Form on Success**:
```javascript
const formRef = useRef();
const [state, formAction] = useActionState(async (prev, formData) => {
const result = await submitForm(prev, formData);
if (result.success) {
formRef.current?.reset();
}
return result;
}, null);
return (
<form ref={formRef} action={formAction}>
...
</form>
);
```
For comprehensive documentation: `research/react-19-comprehensive.md` lines 135-180.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?