Actions and forms
Show pending and result states
Use React form hooks to prevent duplicate work and render server validation messages without rebuilding the submission flow by hand.
A form should explain that work is in progress and show expected server results near the relevant controls.
useFormStatus reads the status of a parent form and is useful in a nested submit button. useActionState keeps the serializable result returned by an action. These hooks require Client Components, but the action itself remains server code. Keep native labels, names, and constraints so the form stays understandable before enhancement.
The status hook must run in a component nested inside the form it observes. It does not describe arbitrary requests elsewhere on the page. Use the pending state to prevent confusing repeat interaction and to announce progress, while keeping navigation and recovery actions available.
Connect field messages to inputs with stable IDs and aria-describedby. Set aria-invalid when a field failed. Keep an unexpected server failure visible until the user retries. A disappearing toast is poor evidence that data was not saved. Move focus after success only when the next task genuinely requires it.
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? 'Saving…' : 'Save'}</button>
}
Wire a slow action and click Save once. The button label should change to Saving… and the control should disable until the response returns. Submit twice quickly anyway and confirm the server still enforces idempotency even when the UI looks blocked.
Render a title field error from useActionState next to the input with aria-describedby. A screen reader should read the error when focus lands on the field.
If useFormStatus sits in the same component as the <form> tag, pending stays false forever. Move the submit button into its own child file and the hook starts working.
Lesson completed