React form state and validation hook with great TypeScript support.
There are no dependencies besides React as a peer dependency.
Check out this repository for a complete Form component built on top of this library and MaterialUI v.4.
yarn add cl-use-form-state
Check out this sandbox for an example
import React from "react";
import { useForm } from "cl-use-form-state";
type FormInputs = {
username: string;
password: string;
age: number;
};
export function Component() {
const {
// an object with the current state of the inputs
inputs,
// a boolean that is true if all inputs are valid
isValid,
// returns an object with every input key along with its current value
getInputValues,
// react on change event handler i.e onChange
onChangeHandler,
// react on focus event handler i.e onBlur
onTouchHandler,
// optional function to update input value
updateInput,
// optional function to (re)set entire form state
setFormState,
} = useForm<FormInputs>((cl) => {
/* useForm takes a function as its argument and that function
receives another function that can be used to create inputs
All defined inputs must be present in the returned object */
return {
username: cl("", { minLength: 1, maxLength: 32 }),
password: cl("", {
minLength: 8,
maxLength: 64,
minNumericalSymbols: 1,
minUppercaseCharacters: 1,
}),
age: cl(20, { minValue: 18 }),
};
});
return <>{...yourJsx}</>;
}cl takes two arguments. An initial value and an object with options for the created input.
| name | type | default | note |
|---|---|---|---|
isValid |
boolean |
false |
- |
isTouched |
boolean |
false |
- |
minLength |
number |
undefined |
- |
maxLength |
number |
undefined |
- |
minValue |
number |
undefined |
- |
maxValue |
number |
undefined |
- |
minUppercaseCharacters |
number |
undefined |
- |
maxUppercaseCharacters |
number |
undefined |
- |
minNumericalSymbols |
number |
undefined |
- |
maxNumericalSymbols |
number |
undefined |
- |
customRule |
(value: InputValueType, state: FormState) => boolean |
undefined |
can be used to create any validation rule for any input field, see here |
connectFields |
string[] |
undefined |
can be used to make fields dependant upon each other, see here |
updateInput can be used to update the value of an input if onChangeHandler cannot be used.
It takes two arguments, the id of the input to change and the new value of that input.
The type of the value argument is inferred from the id. Say we have these simple input types
type FormInputs = {
name: string;
age: number;
};and we use updateInput as an onClick handler. Then this pattern follows:
// Ok
onClick={() => updateInput("name", "someValue")} // expects string
onClick={() => updateInput("age", 21)} // expects number
// Not ok
onClick={() => updateInput("name", 21)} // expects string
onClick={() => updateInput("age", "someValue")} // expects numberA customRule must be a function that takes two arguments, value and state. The value will always be the newest value of the associated input field while the state always will be the newest state of the entire form.
Lets say we have an input where we'd only want to support any given username that starts with C, ends with h and has a maximum length of the current age value:
const form = useForm<{
name: string;
age: number | null;
}>((createInput) => {
return {
name: createInput("", {
customRule: (value, state) => {
const trimmedValue = value.trim();
const length = trimmedValue.length;
return (
length > 0 &&
length <= (state.inputs.age.value || 0) &&
trimmedValue[0] === "C" &&
trimmedValue[length - 1] === "h"
);
},
}),
age: createInput(null, { minValue: 1 }),
};
});The above customRule example has an issue. Lets assume that we have a name that starts with C, ends with h and has a maximum length of the age value. Lets say the name length is 12 and the age value is 15. Now name is a valid input. However, if we change the age value to say 10, then the name value is still valid although it no longer satisfies its own validation constraints.
In this scenario, we want a behavior where each time the value of age changes, the validation for name is re-run. We can achieve this using the connectFields option, that just takes an id of the input we'd like to connect.
const form = useForm<{
name: string;
age: number | null;
}>((createInput) => {
return {
name: createInput("", {
customRule: (value, state) => {
const trimmedValue = value.trim();
const length = trimmedValue.length;
return (
length > 0 &&
length <= (state.inputs.age.value || 0) &&
trimmedValue[0] === "C" &&
trimmedValue[length - 1] === "h"
);
},
}),
age: createInput(null, { minValue: 1, connectFields: ["name"] }),
};
});