Files
2025-11-29 18:22:28 +08:00

3.6 KiB

name, description, allowed-tools, version
name description allowed-tools version
validating-forms Teaches client and server-side form validation patterns in React 19 with Server Actions. Use when implementing form validation, input checking, or error handling. Read, Write, Edit 1.0.0

Form Validation Patterns

React 19 forms require validation on both client and server.

Client-Side Validation

Use HTML5 validation + custom logic:

<form action={submitForm}>
  <input
    name="email"
    type="email"
    required
    pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
  />

  <input
    name="age"
    type="number"
    min="18"
    max="120"
    required
  />

  <button type="submit">Submit</button>
</form>

Server-Side Validation (Required)

Always validate on server - client validation can be bypassed:

'use server';

import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email'),
  age: z.number().min(18, 'Must be 18+').max(120),
  password: z.string().min(8, 'Password must be 8+ characters'),
});

export async function registerUser(previousState, formData) {
  const data = {
    email: formData.get('email'),
    age: Number(formData.get('age')),
    password: formData.get('password'),
  };

  const result = schema.safeParse(data);

  if (!result.success) {
    return {
      errors: result.error.flatten().fieldErrors,
    };
  }

  try {
    const user = await db.users.create({ data: result.data });
    return { success: true, userId: user.id };
  } catch (error) {
    if (error.code === 'P2002') {
      return { errors: { email: ['Email already exists'] } };
    }
    return { error: 'Registration failed' };
  }
}

Display Errors

'use client';

import { useActionState } from 'react';
import { registerUser } from './actions';

export default function RegisterForm() {
  const [state, formAction, isPending] = useActionState(registerUser, null);

  return (
    <form action={formAction}>
      <div>
        <input name="email" type="email" required />
        {state?.errors?.email && (
          <span className="error">{state.errors.email[0]}</span>
        )}
      </div>

      <div>
        <input name="age" type="number" required />
        {state?.errors?.age && (
          <span className="error">{state.errors.age[0]}</span>
        )}
      </div>

      <div>
        <input name="password" type="password" required />
        {state?.errors?.password && (
          <span className="error">{state.errors.password[0]}</span>
        )}
      </div>

      <button type="submit" disabled={isPending}>
        {isPending ? 'Registering...' : 'Register'}
      </button>

      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p>Registration successful!</p>}
    </form>
  );
}

Validation Libraries

Recommended: Zod for type-safe validation:

import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ['confirmPassword'],
});

For comprehensive validation patterns, see: research/react-19-comprehensive.md.

Zod v4 Validation:

  • handling-zod-errors skill from the zod-4 plugin - Advanced error customization with unified error API, custom messages, and error formatting for better user feedback
  • writing-zod-transformations skill from the zod-4 plugin - Built-in string transformations (trim, toLowerCase) for normalizing user input before validation