Skip to main content

Overview

The useFormEngine hook provides a complete form management solution with built-in validation. It handles form state, field-level validation, and change events while automatically compiling validation rules from your form schema.

Import

import useFormEngine from '@app/hooks/useFormEngine'

Type Signature

function useFormEngine<T extends BaseField>(formSchema: T[]): {
  formData: Record<string, string>
  onChange: (e: React.ChangeEvent<HTMLInputElement>, fieldId: string) => void
  errors: Record<string, string>
}

Parameters

formSchema
BaseField[]
required
Array of field configuration objects. Each field must include:
  • id (string): Unique identifier for the field
  • fieldValidators (FieldValidatorType[]): Array of validation rules
Validator Types:
  • { type: "required" } - Field must have a value
  • { type: "minLength", constraints: { minLength: number } } - Minimum character length
  • { type: "maxLength", constraints: { minLength: number } } - Maximum character length

Return Value

formData
Record<string, string>
Object containing current values for all form fields, keyed by field ID.
onChange
(e: React.ChangeEvent<HTMLInputElement>, fieldId: string) => void
Change handler function that updates field values and triggers validation.Parameters:
  • e: React change event from the input element
  • fieldId: The unique identifier of the field being updated
errors
Record<string, string>
Object containing validation error messages for fields that failed validation, keyed by field ID. Empty string indicates no error.

Usage Example

import useFormEngine from '@app/hooks/useFormEngine'

function LoginForm() {
  const formSchema = [
    {
      id: 'email',
      label: 'Email',
      type: 'email',
      placeholder: 'Enter your email',
      fieldValidators: [
        { type: 'required' },
        { type: 'minLength', constraints: { minLength: 5 } }
      ]
    },
    {
      id: 'password',
      label: 'Password',
      type: 'password',
      placeholder: 'Enter your password',
      fieldValidators: [
        { type: 'required' },
        { type: 'minLength', constraints: { minLength: 8 } }
      ]
    }
  ]

  const { formData, onChange, errors } = useFormEngine(formSchema)

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (Object.values(errors).some(err => err)) {
      console.log('Form has errors')
      return
    }
    console.log('Form data:', formData)
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="email"
          value={formData.email || ''}
          onChange={(e) => onChange(e, 'email')}
          placeholder="Email"
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <input
          type="password"
          value={formData.password || ''}
          onChange={(e) => onChange(e, 'password')}
          placeholder="Password"
        />
        {errors.password && <span className="error">{errors.password}</span>}
      </div>
      
      <button type="submit">Submit</button>
    </form>
  )
}

How It Works

  1. Schema Compilation: On initialization, the hook compiles the form schema by attaching validator functions to each field using attachValidators
  2. State Management: Maintains two state objects:
    • formData: Stores current field values
    • errors: Stores validation error messages
  3. Validation Flow: When a field changes:
    • Updates the field value in formData
    • Retrieves the compiled field schema
    • Runs all validators for that field sequentially
    • Stops at the first validation error
    • Updates the errors state with the result
  4. Real-time Feedback: Validation runs on every change, providing immediate feedback to users
type BaseField = {
  id: string
  fieldValidators: FieldValidatorType[]
}

type FieldValidatorType =
  | { type: "required" }
  | { type: "minLength", constraints: { minLength: number } }
  | { type: "maxLength", constraints: { minLength: number } }

type ValidatorFnType = (value: string, fieldName: string) => string | null

Source

Implemented in: src/app/hooks/useFormEngine.tsx:9

Build docs developers (and LLMs) love