1.0.9 • Published 5 months ago

optimal-formik v1.0.9

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

Optimal Formik

A formik alternative that optimizes for performance.

Installation

npm install optimal-formik

Usage

import {
  OptimalFormik,
  Input,
  IterableField,
  IterableRenderer,
  ZodValidator,
} from "optimal-formik";
import { z } from "zod";

const validationSchema = z.object({
  name: z.string().min(3),
  age: z.number().min(18),
  friends: z.array(
    z.object({
      name: z.string().min(3),
      age: z.number().min(18).optional(),
    })
  ),
});

const formValidator = new ZodValidator(validationSchema);

type Data = z.infer<typeof validationSchema>;

const initialValues: Data = {
  name: "John",
  age: 30,
  friends: [
    {
      name: "Jane",
    },
    {
      name: "Bob",
    },
  ],
};

function App() {
  return (
    <>
      <OptimalFormik<Data>
        formID={"test"}
        initialValues={initialValues}
        zodValidationSchema={formValidator}
        onSubmit={(data) => {
          console.log(data);
        }}
      >
        <Input name="name" label="Name" />
        <Input type="number" name="age" label="Age" />
        <IterableField<Data["friends"]> name="friends" label="Friends">
          {({ push, remove, modify, setValue }) => (
            <>
              <IterableRenderer<Data["friends"]>>
                {(path, key) => (
                  <>
                    <Input name={path.concat("name")} label="Name" />
                    <Input
                      type="number"
                      name={path.concat("age")}
                      label="Age"
                    />
                    <button onClick={() => remove(path)}>Remove</button>
                  </>
                )}
              </IterableRenderer>
              <button
                onClick={() =>
                  push({
                    name: "",
                    age: undefined,
                  })
                }
              >
                Add Friend
              </button>
              <button onClick={() => setValue(initialValues.friends)}>
                Reset
              </button>
            </>
          )}
        </IterableField>
      </OptimalFormik>
    </>
  );
}

export default App;

Components and helpers

<OptimalFormik> (form root)

Props

PropTypeDescription
formID (required)stringUnique ID of the form.
initialValues (required)Record<string, any>Initial values of the form.
onSubmit (required)(data: Record<string, any>) => voidfunction that is executed when the submit button is pressed
validatorValidatorCan be ZodValidator, YupValidator or a custom child of Validator
preventSubmitOnEnterbooleanPrevent submit on Enter pressed
classNamestring...

Primary components (with Label and ErrorMessage)

Every element with Label has these props available:

PropTypeDescription
name (required)string \| PathPath to the field
label (required)string \| React.JSX.ElementLabel of the field
hideLabelbooleanHides the fields label
containerClassstringClass name of input's container
labelClassstringClass name of input's label
errorClassstringClass name of ErrorMessage

<Input>

Props

PropTypeDescription
Common props...See in the start of this section
Element propsHTMLInputProps...

<Select>

Props

PropTypeDescription
Common props...See in the start of this section
Element propsHTMLSelectProps...

<TextArea>

Props

PropTypeDescription
Common props...See in the start of this section
Element propsHTMLTextAreaProps...

<CheckboxInput>

Props

hideLabel is not available here

PropTypeDescription
Common props...See in the start of this section
Element propsHTMLInputElement...

<IterableField>

Props

PropTypeDescription
Common props...See in the start of this section
children (required)(...helpers: ...) => React.JSX.ElementThe children is a function providing useful functions helpers to manipulate array and objects.
Element propsHTMLDivProps...
Children Helpers

Where E is an element of A, A is the iterable itself, and Path is a class for strict path management.

Updater<A> is a callback like this (value: A) => void. This provides flexibility, but if you need to overwrite the hole object you need to use setValue.

  /** Push new item to array */
  push(value: E): void;
  /** Insert new item to object */
  insert(key: string, value: E): void;
  /** Remove item from array or object */
  remove(path: Path<A>): void;
  /** Modify object or array */
  modify(updater: Updater<A>): void;
  /** Set value of object or array */
  setValue(value: A): void;

Primary components

<IterableRenderer>

This component don't have Label and ErrorMessage but alway is a child of IterableField. This component works like Array.map.

Props

PropTypeDescription
as (default: div)keyof React.JSX.IntrinsicElementsTag name of this component
classNamestringClass of this component
children (required)(path: Path<E>, key: string \| number) => React.JSX.ElementThe children is a function providing the path of the array or object and the key. The key can be the index of the array or key of the object.

<SubmitButton>

Use this component to fire onSubmit form callback.

Props: HTMLButtonElementProps

Complementary components

Useful to make your own OptimalFormik components

<Label>

Label with ErrorMessage

Props

PropTypeDescription
name (required)string \| PathPath to the field
children (required)string \| React.JSX.ElementLabel of the field
hideLabelbooleanHides the fields label
...HTMLLabelProps...

<ErrorMessage>

Show an error if exist

Props

PropTypeDescription
name (required)string \| PathPath to the field
Element propsHTMLDivProps...

<RedDot>

Returns null if no errors were detected embed in the field, else returns the div element. Useful to show if there are errors in hidden sub-fields.

Props

PropTypeDescription
name (required)string \| PathPath to the field
Element propsHTMLDivProps...

Helpers

Path

class Path {
  constructor(key: string | number, path = "") {}

  concat(key: string | number): Path;

  toString(): string;
}

Validator (abstract class)

Useful if you don't use yup or zod as validation schema.

Obs: In both cases value will be the form current data

type ValidationResult =
  | {
      success: true;
    }
  | {
      success: false;
      errors: {
        path?: (string | number)[];
        message: string;
      }[];
    };

abstract class Validator {
  constructor(protected schema: any) {}

  abstract validate(value: unknown): Promise<ValidationResult>;

  abstract validateAt(path: string, value: unknown): ValidationResult;
}

Use

class CustomValidator extends Validator {
  constructor(protected schema) {
    // ...
  }

  async validate(value: unknown) {
    // return the validation result
  }

  async validateAt(path: string, value: unknown) {
    // return the validation result
  }
}

React Context

  • If you want to get FormID without useFormID or useForm, you can use OptimalFormikContext.
  • If you want to get the current path generated by IterableField you can use PathContext.

Hooks

useFormID

Retrieves the current form ID from the OptimalFormikContext. This ID is defined in the parent OptimalFormik and is used to identify the form.

useForm

type FormConfig<T extends AnyObject> = {
  formID: string;
  initialValues: T;
  onSubmit(values: T): void;
  validator?: Validator;
  preventSubmitOnEnter?: boolean;
};

type FormBasicData<T> = {
  data: T;
  touched: Record<string, boolean | undefined>;
  errors: Record<string, string | undefined>;
  isValidating?: boolean;
  isSubmitting?: boolean;
};

type Form<T extends AnyObject> = FormBasicData<T> & {
  config: FormConfig<T>;
  updateForm(updater: Updater<FormBasicData<T>>): void;
};

Example

type MyForm = {
  title: string
  description: string
  counters: {
    counter1: number
    counter2: number
  }
}

const updateForm = useForm((s: Form<MyForm>) => s.updateForm)
const { data, error, touched } = useForm((s: Form<MyForm>) => ({
  data: s.data.counter1
  error: s.errors['counters.counter1']
  touched: s.touched['counters.counter1']
}))

useField

Like useField from Formik.

// you can import this
enum FieldDataType {
  STRING = "string",
  NUMBER = "number",
  BOOLEAN = "boolean",
}

type InputProps<T> = {
  value: T | undefined;
  checked: T | undefined;
  onChange: ChangeEventHandler<
    HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
  >;
  onBlur: FocusEventHandler;
};

type Meta<T> = {
  data: T;
  error: string | undefined;
  touched: boolean | undefined;
  initialValue: T | null;
};

type Helpers<T> = {
  setValue: (value: T) => void;
  setError: (error: string | undefined) => void;
  setTouched: (touched: boolean) => void;
  validate: () => void;
};

// T is the type of the data and F the type of the form
type useField<T, F> = (
  path: Path<F> | string,
  type?: FieldDataType
) => [InputProps<T>, Meta<T>, Helpers<T>];

useFieldErrorData

type useFieldErrorData = (path: Path<AnyObject> | string) => {
  error?: string;
  touched?: boolean;
};

useEmbedErrorsCheck

Returns true if some error is inside the selected path.

type useEmbedErrorsCheck = (path: Path<AnyObject> | string) => boolean;
1.0.9

5 months ago

1.0.8

5 months ago

1.0.4

6 months ago

1.0.1

6 months ago

1.0.0

6 months ago