A formik alternative that optimizes for performance.
npm install optimal-formikimport {
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;| Prop | Type | Description |
|---|---|---|
formID (required) |
string |
Unique ID of the form. |
initialValues (required) |
Record<string, any> |
Initial values of the form. |
onSubmit (required) |
(data: Record<string, any>) => void |
function that is executed when the submit button is pressed |
validator |
Validator |
Can be ZodValidator, YupValidator or a custom child of Validator |
preventSubmitOnEnter |
boolean |
Prevent submit on Enter pressed |
className |
string |
... |
Every element with Label has these props available:
| Prop | Type | Description |
|---|---|---|
name (required) |
string | Path |
Path to the field |
label (required) |
string | React.JSX.Element |
Label of the field |
hideLabel |
boolean |
Hides the fields label |
containerClass |
string |
Class name of input's container |
labelClass |
string |
Class name of input's label |
errorClass |
string |
Class name of ErrorMessage |
| Prop | Type | Description |
|---|---|---|
| Common props | ... | See in the start of this section |
| Element props | HTMLInputProps |
... |
| Prop | Type | Description |
|---|---|---|
| Common props | ... | See in the start of this section |
| Element props | HTMLSelectProps |
... |
| Prop | Type | Description |
|---|---|---|
| Common props | ... | See in the start of this section |
| Element props | HTMLTextAreaProps |
... |
hideLabel is not available here
| Prop | Type | Description |
|---|---|---|
| Common props | ... | See in the start of this section |
| Element props | HTMLInputElement |
... |
| Prop | Type | Description |
|---|---|---|
| Common props | ... | See in the start of this section |
children (required) |
(...helpers: ...) => React.JSX.Element |
The children is a function providing useful functions helpers to manipulate array and objects. |
| Element props | HTMLDivProps |
... |
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;This component don't have Label and ErrorMessage but alway is a child of IterableField. This component works like Array.map.
| Prop | Type | Description |
|---|---|---|
as (default: div) |
keyof React.JSX.IntrinsicElements |
Tag name of this component |
className |
string |
Class of this component |
children (required) |
(path: Path<E>, key: string | number) => React.JSX.Element |
The 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. |
Use this component to fire onSubmit form callback.
Props: HTMLButtonElementProps
Useful to make your own OptimalFormik components
Label with ErrorMessage
| Prop | Type | Description |
|---|---|---|
name (required) |
string | Path |
Path to the field |
children (required) |
string | React.JSX.Element |
Label of the field |
hideLabel |
boolean |
Hides the fields label |
... |
HTMLLabelProps |
... |
Show an error if exist
| Prop | Type | Description |
|---|---|---|
name (required) |
string | Path |
Path to the field |
| Element props | HTMLDivProps |
... |
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.
| Prop | Type | Description |
|---|---|---|
name (required) |
string | Path |
Path to the field |
| Element props | HTMLDivProps |
... |
class Path {
constructor(key: string | number, path = "") {}
concat(key: string | number): Path;
toString(): string;
}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;
}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
}
}- If you want to get FormID without
useFormIDoruseForm, you can useOptimalFormikContext. - If you want to get the current path generated by IterableField you can use
PathContext.
Retrieves the current form ID from the OptimalFormikContext.
This ID is defined in the parent OptimalFormik and is used to identify the form.
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;
};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']
}))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>];type useFieldErrorData = (path: Path<AnyObject> | string) => {
error?: string;
touched?: boolean;
};Returns true if some error is inside the selected path.
type useEmbedErrorsCheck = (path: Path<AnyObject> | string) => boolean;