All files / src/state formState.ts

93.54% Statements 29/31
80% Branches 8/10
93.33% Functions 14/15
93.33% Lines 28/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101        157x       125x       20x                             20x 20x 20x 20x 20x     20x 2x   18x       20x   21x               114x                     9x       9x       9x       4x 17x 11x 11x           3x 3x         11x       2x 2x 2x 2x        
import type { FieldErrors, FormStateSnapshot, FormValues, NormalizedFormSchema } from "../schema/types";
import { getInitialValues } from "../schema/normalizeSchema";
 
function cloneValues(values: FormValues): FormValues {
  return { ...values };
}
 
function cloneErrors(errors: FieldErrors): FieldErrors {
  return Object.fromEntries(Object.entries(errors).map(([fieldName, messages]) => [fieldName, [...messages]]));
}
 
function areEqualValues(left: unknown, right: unknown): boolean {
  return left === right;
}
 
export interface FormState {
  getValues(): FormValues;
  getInitialValues(): FormValues;
  getSnapshot(visibleFields?: string[]): FormStateSnapshot;
  setValue(fieldName: string, value: FormValues[string]): void;
  setValues(values: FormValues): void;
  markTouched(fieldName: string): void;
  setErrors(errors: FieldErrors): void;
  reset(): void;
}
 
export function createFormState(schema: NormalizedFormSchema, initialValues: FormValues = {}): FormState {
  const initial = getInitialValues(schema, initialValues);
  let values = cloneValues(initial);
  const touchedFields = new Set<string>();
  const dirtyFields = new Set<string>();
  let errors: FieldErrors = {};
 
  function recomputeDirty(fieldName: string): void {
    if (areEqualValues(values[fieldName], initial[fieldName])) {
      dirtyFields.delete(fieldName);
    } else {
      dirtyFields.add(fieldName);
    }
  }
 
  return {
    getValues() {
      return cloneValues(values);
    },
 
    getInitialValues() {
      return cloneValues(initial);
    },
 
    getSnapshot(visibleFields: string[] = schema.fieldOrder) {
      return {
        values: cloneValues(values),
        touchedFields: [...touchedFields],
        dirtyFields: [...dirtyFields],
        errors: cloneErrors(errors),
        isValid: Object.keys(errors).length === 0,
        visibleFields: [...visibleFields]
      };
    },
 
    setValue(fieldName, value) {
      Iif (!schema.fieldMap.has(fieldName)) {
        return;
      }
 
      values = {
        ...values,
        [fieldName]: value
      };
      recomputeDirty(fieldName);
    },
 
    setValues(nextValues) {
      schema.fieldOrder.forEach((fieldName) => {
        if (Object.prototype.hasOwnProperty.call(nextValues, fieldName)) {
          values[fieldName] = nextValues[fieldName];
          recomputeDirty(fieldName);
        }
      });
    },
 
    markTouched(fieldName) {
      Eif (schema.fieldMap.has(fieldName)) {
        touchedFields.add(fieldName);
      }
    },
 
    setErrors(nextErrors) {
      errors = cloneErrors(nextErrors);
    },
 
    reset() {
      values = cloneValues(initial);
      touchedFields.clear();
      dirtyFields.clear();
      errors = {};
    }
  };
}