Forms and feedback
Show pending, success, and failure
Prevent duplicate submissions and communicate asynchronous results in the interface without losing the user’s data or context.
8 minute lesson
The form does not finish the instant we click its button. While the request runs, we need to show that something is happening.
useFormStatus() gives a component the status of its parent form. We can use it to disable the submit button and change its text.
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<Button type='submit' disabled={pending}>
{pending ? 'Creating…' : 'Create project'}
</Button>
)
}
Put this component inside the form. useFormStatus() reads the nearest parent form, so it will not work if the button lives outside it.
Keep server errors visible near the form. A toast is fine for a small success message, but do not make a disappearing toast the only place where you explain a failure.
Pending UI reduces accidental repeats but does not guarantee exactly-once execution. Network retries, double submissions before state updates, and back-button resubmission still happen. Protect important operations with a unique constraint or idempotency key.
A network failure can leave the outcome unknown: the server may have committed before its response was lost. Do not claim “nothing was saved” unless you know that. Preserve entered values for correctable failures and offer a safe retry or refresh path. Success should update the durable interface or navigate to the created resource; a toast can reinforce that evidence.
Add success, known rejection, and unknown network-outcome modes to the delayed action. Submit twice and retry. The button should explain progress, values should survive failure, important messages should persist, and one idempotency key should create at most one project.
Lesson completed