How to Validate Forms in React with Custom Hooks

Introduction

Form validation is a critical part of any web application. In React, building a reusable validation hook gives you type safety, real-time feedback, and clean separation of concerns.

Step 1: Define Validation Rules

Start by creating a TypeScript interface for your validation rules:

interface ValidationRule {
  required?: boolean;
  minLength?: number;
  pattern?: RegExp;
  message: string;
}

Each rule can combine multiple constraints. The message field provides user-friendly error text.

Step 2: Build the useForm Hook

The hook manages form state, validation, and submission:

export function useForm<T extends Record<string, string>>(
  initialValues: T,
  rules: Partial<Record<keyof T, ValidationRule[]>>,
) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});

  const validate = useCallback((name: keyof T, value: string) => {
    const fieldRules = rules[name];
    if (!fieldRules) return '';
    for (const rule of fieldRules) {
      if (rule.required && !value) return rule.message;
      if (rule.minLength && value.length < rule.minLength) return rule.message;
      if (rule.pattern && !rule.pattern.test(value)) return rule.message;
    }
    return '';
  }, [rules]);

  return { values, errors, handleChange, isValid };
}

Step 3: Integrate with Components

Wire the hook into your form component:

function LoginForm() {
  const { values, errors, handleChange, isValid } = useForm(
    { email: '', password: '' },
    {
      email: [{ required: true, message: 'Email is required' }],
      password: [{ minLength: 8, message: 'Min 8 characters' }],
    },
  );
  return <form onSubmit={...}>...</form>;
}

Conclusion

This pattern gives you reusable, type-safe form validation that works across any React form. The hook is composable — you can extend it with async validation, debouncing, or nested form support.