CodShot
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>
  );
}

Snippet actions

CodShot user
CodShot user
Updated: 2026-08-06 18:36
Public snippets
0
Forks
CodShot AI Help
Ask about CodShot features, plans, workspace, AI limits, referrals, and public help topics.
Hi! I can help you understand how CodShot works. What would you like to know?
For account-specific or sensitive issues, please open a support ticket. Open support