REACT COOL FORM

React hooks for forms state, validation, and performance.

Milestone

  • [x] Core features
  • [x] Type definition
  • [x] Support server-side rendering
  • [x] CI/CD
  • [ ] Documentation
  • [ ] Examples
  • [ ] Unit testing
  • [ ] End to end testing

Getting Started

To use react-cool-form, you must use [email protected] or greater which includes hooks.

You can install this package via npm.

$ yarn add react-cool-form
# or
$ npm install --save react-cool-form

Here's the basic example of how does it works, full documentation will be provided soon. If you have any question, feel free to ask me.

import { useForm } from "react-cool-form";

const App = () => {
  const { formRef, getState } = useForm({
    // Provide the default values for our form state
    defaultValues: { name: "", email: "", password: "" },
    // The event only triggered when the form is valid
    onSubmit: (values, actions) => {
      console.log("onSubmit: ", values);
    },
  });

  const errors = getState(
    "errors",
    // The second argument is the "watch" mode, it can be: { filterUntouchedErrors: true } | false
    // Default as below, which helps you display an error until filed is blurred for better UX
    { filterUntouchedErrors: true }
  );

  return (
    <form ref={formRef} noValidate>
      <label>Name</label>
      {/* Support built-in validation attributes */}
      <input name="name" required />
      {errors.name && <p>{errors.name}</p>}

      <label>Email</label>
      <input name="email" type="email" required />
      {errors.email && <p>{errors.email}</p>}

      <label>Password</label>
      <input name="password" type="password" required minLength={8} />
      {errors.password && <p>{errors.password}</p>}

      <input type="reset" />
      <input type="submit" />
    </form>
  );
};

The form state of the above example will look something like this:

{
  "values": {
    "name": "Welly",
    "email": "[email protected]",
    "password": "12345"
  },
  "touched": {
    "name": true,
    "email": true,
    "password": true
  },
  "isValidating": false,
  "isValid": false,
  "errors": {
    "password": "Please lengthen this text to 8 characters or more"
  },
  "isDirty": true,
  "dirtyFields": {
    "name": true,
    "email": true,
    "password": true
  },
  "isSubmitting": false,
  "isSubmitted": false,
  "submitCount": 1
}

Super easy right? The above example is just the tip of the iceberg. react-cool-form is a lightweight and powerful form library. you can understand the API by its type definition atm to see how it rocks 🤘🏻.

import { FocusEvent, RefObject, SyntheticEvent } from "react";

type FormValues = Record<string, any>;

type DeepProps<V, T = any> = {
  [K in keyof V]?: V[K] extends T ? T : DeepProps<V[K]>;
};

type Errors<V> = DeepProps<V>;

type FormState<V = FormValues> = Readonly<{
  values: V;
  touched: DeepProps<V, boolean>;
  errors: Errors<V>;
  isDirty: boolean;
  dirtyFields: DeepProps<V, boolean>;
  isValidating: boolean;
  isValid: boolean;
  isSubmitting: boolean;
  isSubmitted: boolean;
  submitCount: number;
}>;

type Options<V> = Omit<
  Return<V>,
  "formRef" | "validate" | "submit" | "controller"
>;

interface OnReset<V = FormValues> {
  (
    values: V,
    options: Omit<Options<V>, "reset">,
    event?: Event | SyntheticEvent<any>
  ): void;
}

interface OnSubmit<V = FormValues> {
  (
    values: V,
    options: Options<V>,
    event?: Event | SyntheticEvent<any>
  ): void | Promise<void>;
}

interface OnError<V = FormValues> {
  (
    errors: Errors<V>,
    options: Options<V>,
    event?: Event | SyntheticEvent<any>
  ): void;
}

interface Debug<V> {
  (formState: FormState<V>): void;
}

interface FormValidator<V = FormValues> {
  (values: V): Errors<V> | void | Promise<Errors<V> | void>;
}

interface FieldValidator<V = FormValues> {
  (value: any, values: V): any | Promise<any>;
}

interface ValidateRef<V> {
  (validate: FieldValidator<V>): (
    field: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null
  ) => void;
}

interface GetState {
  (
    path: string | string[] | Record<string, string>,
    watch?: { filterUntouchedErrors: boolean } | false
  ): any;
}

interface SetErrors<V> {
  (
    errors?: Errors<V> | ((previousErrors: Errors<V>) => Errors<V> | undefined)
  ): void;
}

interface SetFieldError {
  (name: string, error?: any | ((previousError?: any) => any)): void;
}

type ValuesArg<V> = V | ((previousValues: V) => V);

interface SetValues<V> {
  (
    values: ValuesArg<V>,
    options?: {
      shouldValidate?: boolean;
      touchedFields?: string[];
      dirtyFields?: string[];
    }
  ): void;
}

interface SetFieldValue {
  (
    name: string,
    value: any | ((previousValue: any) => any),
    options?: {
      [k in "shouldValidate" | "shouldTouched" | "shouldDirty"]?: boolean;
    }
  ): void;
}

interface ValidateForm<V> {
  (): Promise<Errors<V>>;
}

interface ValidateField<V> {
  (name: string): Promise<Errors<V>>;
}

interface Reset<V> {
  (
    values?: ValuesArg<V> | null,
    exclude?: (keyof FormState<V>)[] | null,
    event?: SyntheticEvent<any>
  ): void;
}

interface Submit<V> {
  (event?: SyntheticEvent<any>): Promise<{ values?: V; errors?: Errors<V> }>;
}

interface Parse<E = any, R = any> {
  (event: E): R;
}

interface Format<V = any, R = any> {
  (value: V): R;
}

interface OnChange<E = any> {
  (event: E, value?: any): void;
}

interface OnBlur {
  (event: FocusEvent<any>): void;
}

interface Controller<V = FormValues, E = any> {
  (
    name: string,
    options?: {
      validate?: FieldValidator<V>;
      value?: any;
      defaultValue?: any;
      parse?: Parse<E>;
      format?: Format;
      onChange?: OnChange<E>;
      onBlur?: OnBlur;
    }
  ):
    | {
        name: string;
        value: any;
        onChange: (event: E) => void;
        onBlur: OnBlur;
      }
    | Record<string, unknown>;
}

interface Config<V = FormValues> {
  defaultValues: V;
  validate?: FormValidator<V>;
  validateOnChange?: boolean;
  validateOnBlur?: boolean;
  ignoreFields?: string[];
  onReset?: OnReset<V>;
  onSubmit?: OnSubmit<V>;
  onError?: OnError<V>;
  debug?: Debug<V>;
}

interface Return<V = FormValues> {
  formRef: RefObject<HTMLFormElement>;
  validate: ValidateRef<V>;
  getState: GetState;
  setErrors: SetErrors<V>;
  setFieldError: SetFieldError;
  setValues: SetValues<V>;
  setFieldValue: SetFieldValue;
  validateForm: ValidateForm<V>;
  validateField: ValidateField<V>;
  reset: Reset<V>;
  submit: Submit<V>;
  controller: Controller<V>;
}

const useForm: <V extends FormValues = FormValues>(
  config: Config<V>
) => Return<V>;

const get: (object: any, path: string, defaultValue?: unknown) => any;

const set: (
  object: any,
  path: string,
  value: unknown,
  immutable?: boolean
) => any;

GitHub