TSX
Public
Next.js Server Action Form with useActionState
Connect a Next.js Server Action to a client form with validation and pending state.
#nextjs
#server-actions
#form
#typescript
TSX
// app/actions.ts
'use server';
export type CreateUserState = {
message: string;
errors: {
email?: string;
};
};
export async function createUser(
previousState: CreateUserState,
formData: FormData,
): Promise<CreateUserState> {
const email = String(formData.get('email') ?? '').trim();
if (!email.includes('@')) {
return {
message: '',
errors: { email: 'Enter a valid email address.' },
};
}
await db.user.create({
data: { email },
});
return {
message: 'User created.',
errors: {},
};
}
// app/components/create-user-form.tsx
'use client';
import { useActionState } from 'react';
import {
createUser,
type CreateUserState,
} from '@/app/actions';
const initialState: CreateUserState = {
message: '',
errors: {},
};
export function CreateUserForm() {
const [state, formAction, isPending] = useActionState(
createUser,
initialState,
);
return (
<form action={formAction}>
<input name="email" type="email" required />
<button disabled={isPending}>
{isPending ? 'Creating…' : 'Create user'}
</button>
<p aria-live="polite">
{state.errors.email ?? state.message}
</p>
</form>
);
}