Types
Validator
The only exported type.
type Validator<Element> = Falsy | ((el: Element) => ValidatorResponse);
The element type is a parameter, so annotate the element you are actually attaching to and get a typed argument:
const minLength: Validator<HTMLInputElement> = el =>
el.value.length < 5 && 'Use at least 5 characters';
const hasSelection: Validator<HTMLSelectElement> = el =>
el.selectedIndex === 0 && 'Pick an option';
Falsy is part of the union so a conditional entry type-checks without a cast:
<input ref={validate(() => [required, needsMatch() && mustMatch])} />
Falsy
type Falsy = false | 0 | '' | null | undefined | void;
Everything that counts as passing. void is in the union so a validator that just runs if (bad) return message type-checks without an explicit return undefined.
ValidatorResponse
type ValidatorResponse = MaybePromise<string | Falsy>;
A message, nothing, or a promise of either.
Typing errors
useForm<ErrorFields> sets the shape of the store:
type SignupFields = {
email: string;
password: string;
form: string;
};
const { errors } = useForm<SignupFields>();
Include your form-level keys in the type. They are not tied to any field, but they are keys in the same store, and leaving them out makes errors.form a type error.
validateField and getFieldValue both take keyof ErrorFields, so a form-level key is accepted there too even though no element is registered under it. Both are defined to handle a missing field: validateField resolves false and getFieldValue returns undefined.