Claude Code Plugins

Community-maintained marketplace

Feedback

Implement form validation using React Hook Form, Formik, Vee-Validate, and custom validators. Use when building robust form handling with real-time validation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name form-validation
description Implement form validation using React Hook Form, Formik, Vee-Validate, and custom validators. Use when building robust form handling with real-time validation.

Form Validation

Overview

Implement comprehensive form validation including client-side validation, server-side synchronization, and real-time error feedback with TypeScript type safety.

When to Use

  • User input validation
  • Form submission handling
  • Real-time error feedback
  • Complex validation rules
  • Multi-step forms

Implementation Examples

1. React Hook Form with TypeScript

// types/form.ts
export interface LoginFormData {
  email: string;
  password: string;
  rememberMe: boolean;
}

export interface RegisterFormData {
  email: string;
  password: string;
  confirmPassword: string;
  name: string;
  terms: boolean;
}

// components/LoginForm.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
import { LoginFormData } from '../types/form';

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export const LoginForm: React.FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    watch
  } = useForm<LoginFormData>({
    defaultValues: {
      email: '',
      password: '',
      rememberMe: false
    }
  });

  const onSubmit: SubmitHandler<LoginFormData> = async (data) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(data)
      });
      if (!response.ok) throw new Error('Login failed');
      // Handle success
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Email</label>
        <input
          type="email"
          {...register('email', {
            required: 'Email is required',
            pattern: {
              value: emailRegex,
              message: 'Invalid email format'
            }
          })}
        />
        {errors.email && <span className="error">{errors.email.message}</span>}
      </div>

      <div>
        <label>Password</label>
        <input
          type="password"
          {...register('password', {
            required: 'Password is required',
            minLength: {
              value: 8,
              message: 'Password must be at least 8 characters'
            }
          })}
        />
        {errors.password && <span className="error">{errors.password.message}</span>}
      </div>

      <div>
        <label>
          <input type="checkbox" {...register('rememberMe')} />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
};

// Custom validator
const usePasswordStrength = () => {
  return (password: string): boolean | string => {
    if (password.length < 8) return 'At least 8 characters';
    if (!/[A-Z]/.test(password)) return 'At least one uppercase letter';
    if (!/[0-9]/.test(password)) return 'At least one number';
    return true;
  };
};

2. Formik with Yup Validation

// validationSchema.ts
import * as Yup from 'yup';

export const registerValidationSchema = Yup.object().shape({
  email: Yup.string()
    .email('Invalid email')
    .required('Email is required'),
  password: Yup.string()
    .min(8, 'Password must be at least 8 characters')
    .matches(/[A-Z]/, 'Must contain uppercase letter')
    .matches(/[0-9]/, 'Must contain number')
    .required('Password is required'),
  confirmPassword: Yup.string()
    .oneOf([Yup.ref('password')], 'Passwords must match')
    .required('Confirm password is required'),
  name: Yup.string()
    .min(2, 'Name too short')
    .required('Name is required'),
  terms: Yup.boolean()
    .oneOf([true], 'You must accept terms')
    .required()
});

// components/RegisterForm.tsx
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { registerValidationSchema } from '../validationSchema';
import { RegisterFormData } from '../types/form';

export const RegisterForm: React.FC = () => {
  const initialValues: RegisterFormData = {
    email: '',
    password: '',
    confirmPassword: '',
    name: '',
    terms: false
  };

  const handleSubmit = async (
    values: RegisterFormData,
    { setSubmitting, setFieldError }: any
  ) => {
    try {
      const response = await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify(values)
      });

      if (!response.ok) {
        const error = await response.json();
        if (error.emailExists) {
          setFieldError('email', 'Email already registered');
        }
        throw new Error(error.message);
      }
    } catch (error) {
      console.error(error);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Formik
      initialValues={initialValues}
      validationSchema={registerValidationSchema}
      onSubmit={handleSubmit}
    >
      {({ isSubmitting, isValid }) => (
        <Form>
          <div>
            <label htmlFor="name">Name</label>
            <Field name="name" type="text" />
            <ErrorMessage name="name" component="span" className="error" />
          </div>

          <div>
            <label htmlFor="email">Email</label>
            <Field name="email" type="email" />
            <ErrorMessage name="email" component="span" className="error" />
          </div>

          <div>
            <label htmlFor="password">Password</label>
            <Field name="password" type="password" />
            <ErrorMessage name="password" component="span" className="error" />
          </div>

          <div>
            <label htmlFor="confirmPassword">Confirm Password</label>
            <Field name="confirmPassword" type="password" />
            <ErrorMessage name="confirmPassword" component="span" className="error" />
          </div>

          <div>
            <label>
              <Field name="terms" type="checkbox" />
              I agree to terms
            </label>
            <ErrorMessage name="terms" component="span" className="error" />
          </div>

          <button type="submit" disabled={isSubmitting || !isValid}>
            {isSubmitting ? 'Registering...' : 'Register'}
          </button>
        </Form>
      )}
    </Formik>
  );
};

3. Vue Vee-Validate

// validationRules.ts
import { defineRule } from 'vee-validate';
import { email, required, min, confirmed } from '@vee-validate/rules';

defineRule('required', required);
defineRule('email', email);
defineRule('min', min);
defineRule('confirmed', confirmed);
defineRule('password-strength', (value: string) => {
  if (value.length < 8) return 'Password must be at least 8 characters';
  if (!/[A-Z]/.test(value)) return 'Must contain uppercase letter';
  if (!/[0-9]/.test(value)) return 'Must contain number';
  return true;
});

// components/LoginForm.vue
<template>
  <Form @submit="onSubmit" :validation-schema="validationSchema">
    <div class="form-group">
      <label for="email">Email</label>
      <Field name="email" type="email" as="input" class="form-control" />
      <ErrorMessage name="email" class="error" />
    </div>

    <div class="form-group">
      <label for="password">Password</label>
      <Field name="password" type="password" as="input" class="form-control" />
      <ErrorMessage name="password" class="error" />
    </div>

    <button type="submit" :disabled="isSubmitting">
      {{ isSubmitting ? 'Logging in...' : 'Login' }}
    </button>
  </Form>
</template>

<script setup lang="ts">
import { Form, Field, ErrorMessage } from 'vee-validate';
import { object, string } from 'yup';
import { ref } from 'vue';

const isSubmitting = ref(false);

const validationSchema = object({
  email: string().email('Invalid email').required('Email is required'),
  password: string().required('Password is required')
});

const onSubmit = async (values: any) => {
  isSubmitting.value = true;
  try {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(values)
    });
    if (!response.ok) throw new Error('Login failed');
  } catch (error) {
    console.error(error);
  } finally {
    isSubmitting.value = false;
  }
};
</script>

4. Custom Validator Hook

// hooks/useFieldValidator.ts
import { useState, useCallback } from 'react';

export interface ValidationRule {
  validate: (value: any) => boolean | string;
  message: string;
}

export interface FieldError {
  isValid: boolean;
  message: string | null;
}

export const useFieldValidator = (rules: ValidationRule[] = []) => {
  const [error, setError] = useState<FieldError>({
    isValid: true,
    message: null
  });

  const validate = useCallback((value: any) => {
    for (const rule of rules) {
      const result = rule.validate(value);
      if (result !== true) {
        setError({
          isValid: false,
          message: typeof result === 'string' ? result : rule.message
        });
        return false;
      }
    }

    setError({
      isValid: true,
      message: null
    });
    return true;
  }, [rules]);

  const clearError = useCallback(() => {
    setError({
      isValid: true,
      message: null
    });
  }, []);

  return { error, validate, clearError };
};

// Usage
const { error: emailError, validate: validateEmail } = useFieldValidator([
  {
    validate: (v) => v.length > 0,
    message: 'Email is required'
  },
  {
    validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
    message: 'Invalid email format'
  }
]);

5. Server-Side Validation Integration

// Async server validation
const useAsyncValidation = () => {
  const validateEmail = async (email: string) => {
    const response = await fetch(`/api/validate/email?email=${email}`);
    const { available } = await response.json();
    return available ? true : 'Email already registered';
  };

  const validateUsername = async (username: string) => {
    const response = await fetch(`/api/validate/username?username=${username}`);
    const { available } = await response.json();
    return available ? true : 'Username taken';
  };

  return { validateEmail, validateUsername };
};

// React Hook Form with async validation
const { validateEmail } = useAsyncValidation();

register('email', {
  required: 'Email required',
  validate: async (value) => {
    return await validateEmail(value);
  }
});

Best Practices

  • Validate on both client and server
  • Provide real-time feedback
  • Use TypeScript for type safety
  • Implement custom validators for complex rules
  • Handle async validation properly
  • Show clear error messages
  • Preserve user input on validation failure
  • Test validation rules thoroughly
  • Use schema validation (Yup, Zod)

Resources