Real World Example

Form State From a Model

Form libraries like React Hook Form and Formik derive the value, error and touched types of a form from one model type. A mapped type generates all of them, so adding a field to the model updates the form automatically.

Example

type CheckoutForm = { fullName: string; phone: string; pincode: string; saveAddress: boolean };

type FormErrors<T> = { [K in keyof T]?: string };
type Touched<T> = { [K in keyof T]: boolean };
type Validators<T> = { [K in keyof T]?: (value: T[K]) => string | undefined };

const validators: Validators<CheckoutForm> = {
  fullName: (v) => (v.trim().length < 3 ? "Enter your full name" : undefined),
  phone: (v) => (/^[6-9]\d{9}$/.test(v) ? undefined : "Enter a valid 10-digit mobile number"),
  pincode: (v) => (/^\d{6}$/.test(v) ? undefined : "PIN code must be 6 digits"),
};

function validate<T extends object>(values: T, rules: Validators<T>): FormErrors<T> {
  const errors: FormErrors<T> = {};
  for (const key of Object.keys(values) as (keyof T)[]) {
    const message = rules[key]?.(values[key]);
    if (message) errors[key] = message;
  }
  return errors;
}

const values: CheckoutForm = { fullName: "Ravi Teja", phone: "12345", pincode: "50008", saveAddress: true };
const touched: Touched<CheckoutForm> = { fullName: true, phone: true, pincode: false, saveAddress: false };
const errors = validate(values, validators);

for (const [field, message] of Object.entries(errors)) {
  const shown = touched[field as keyof CheckoutForm] ? "shown " : "hidden";
  console.log(shown, field.padEnd(8), message);
}

Output

shown  phone    Enter a valid 10-digit mobile number
hidden pincode  PIN code must be 6 digits