REACT Contents

Form Typing Patterns

Keep form state separate from domain models. Use typed validators, structured error maps, and clear draft vs validated states to avoid Partial abuse and invalid domain objects in UI.

On this page

Form State Is Not Domain State

  • Form inputs are drafts and can be incomplete.
  • Domain models represent validated invariants.
  • Production rule: never reuse domain types for draft input state.

Recommended Modeling

  • FormState: all fields as strings or input friendly types.
  • Domain: validated types used by business logic and API calls.
  • Errors: structured map keyed by field name, plus a global message.

Example: Draft vs Domain

type SignupFormState = {
  email: string;
  password: string;
};

type SignupDomain = {
  email: string;
  passwordHash: string;
};

type FieldErrors = Partial<Record<keyof SignupFormState, string>>;

Typed Validation

function validateDraft(d: SignupFormState): { ok: true; value: SignupFormState } | { ok: false; errors: FieldErrors } {
  const errors: FieldErrors = {};

  if (!d.email.includes("@")) errors.email = "Invalid email";
  if (d.password.length < 8) errors.password = "Minimum length is 8";

  if (Object.keys(errors).length > 0) return { ok: false, errors };
  return { ok: true, value: d };
}

Submit Pipeline

  • Validate draft.
  • Map to API payload.
  • Send request with in flight guard.
  • Normalize server errors back into FieldErrors.

Failure Modes

  • Using Partial for domain types and allowing missing required fields.
  • Errors stored as unstructured strings that cannot map to fields.
  • Client validation differs from server, producing confusing UX.
  • Form state too broad causing re render lag.

Production Checklist

  • Form draft types are separate from domain models.
  • Validation produces structured errors.
  • Submit is guarded against double submit.
  • Server errors map to fields consistently.