Loading lessons...
Form Handling & useActionState
Form Handling & useActionState
Handling form submission states, validation errors, and pending indicators in modern Next.js is powered by React's useActionState and useFormStatus hooks.
1. Managing Action State with useActionState
The useActionState hook manages asynchronous form state, returning validation messages or result objects from a Server Action.
// app/actions.ts
'use server';
export type FormState = {
message: string;
errors?: { email?: string; password?: string };
};
export async function loginUser(prevState: FormState, formData: FormData): Promise<FormState> {
const email = formData.get('email') as string;
const password = formData.get('password') as string;
if (!email.includes('@')) {
return { message: 'Validation Failed', errors: { email: 'Invalid email address' } };
}
// Perform authentication...
return { message: 'Successfully Logged In!' };
}
2. Client Form Component Implementation
// components/LoginForm.tsx
'use client';
import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
import { loginUser, FormState } from '@/app/actions';
const initialState: FormState = { message: '' };
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className="w-full bg-blue-600 text-white py-2 rounded disabled:opacity-50"
>
{pending ? 'Logging in...' : 'Sign In'}
</button>
);
}
export default function LoginForm() {
const [state, formAction] = useActionState(loginUser, initialState);
return (
<form action={formAction} className="max-w-md space-y-4">
<div>
<label className="block text-sm font-medium">Email</label>
<input name="email" type="email" className="w-full border p-2 rounded" />
{state.errors?.email && (
<p className="text-red-500 text-xs mt-1">{state.errors.email}</p>
)}
</div>
<SubmitButton />
{state.message && <p className="text-sm font-bold">{state.message}</p>}
</form>
);
}
Important Rules for useFormStatus
⚠️ DocHero Safety Disclaimer
The useFormStatus hook must be called from a child component rendered inside the <form>. Calling it in the component containing the <form> tag will return pending = false!
Form Hook Comparison
| Hook Name | Import Source | Purpose |
|---|---|---|
useActionState(action, initial) | react | Connects action to component state & returns [state, formAction] |
useFormStatus() | react-dom | Returns { pending, data, method, action } status of parent form |
TL;DR
useActionStateconnects Server Actions to local component state to display validation errors.- Action functions used with
useActionStatereceive(prevState, formData)as arguments. useFormStatusprovides thependingstate of a form for submit loading spinners.useFormStatusmust be placed inside a child component nested inside the target<form>.