useActionState is a Hook that manages form state based on the result of a form action. It's designed to work seamlessly with Server Actions and progressive enhancement.
Basic Usage
tsximport { useActionState } from 'react'; async function submitForm(previousState: State, formData: FormData) { const email = formData.get('email') as string; // Validate if (!email.includes('@')) { return { error: 'Invalid email', email }; } // Submit to server await saveEmail(email); return { success: true, email }; } function NewsletterForm() { const [state, formAction, isPending] = useActionState(submitForm, { error: null, success: false, email: '' }); return ( <form action={formAction}> <input name="email" type="email" defaultValue={state.email} disabled={isPending} /> {state.error && <p className="error">{state.error}</p>} {state.success && <p className="success">Subscribed!</p>} <button type="submit" disabled={isPending}> {isPending ? 'Subscribing...' : 'Subscribe'} </button> </form> ); }
How It Works
- Initial render:
stateequalsinitialState - Form submission: Action is called with
(previousState, formData) - Action completes:
stateupdates to the returned value - isPending:
truewhile the action is running
With Server Actions
tsx// actions.ts 'use server'; export async function createPost(prevState: State, formData: FormData) { const title = formData.get('title') as string; const content = formData.get('content') as string; try { await db.posts.create({ title, content }); return { success: true, error: null }; } catch (e) { return { success: false, error: 'Failed to create post' }; } } // Component 'use client'; import { useActionState } from 'react'; import { createPost } from './actions'; function PostForm() { const [state, action, pending] = useActionState(createPost, { success: false, error: null }); return ( <form action={action}> <input name="title" required /> <textarea name="content" required /> <button disabled={pending}> {pending ? 'Creating...' : 'Create Post'} </button> {state.error && <p>{state.error}</p>} </form> ); }
Progressive Enhancement
Forms using useActionState work even before JavaScript loads:
tsxconst [state, action] = useActionState(serverAction, initialState, '/posts'); // ^^^^^^^^ // Optional permalink for no-JS