import React, { useState } from 'react' export type useFormExtractor = (from: any) => any export type useFormAutoOptions = { getter?: string, setter?: string, extractor?: useFormExtractor } export type useFormOptions = { extractor?: useFormExtractor, } export type useFormValidatorFunction = ((s: any) => boolean | Promise) export type useFormValidatorMethod = useFormValidatorFunction | RegExp export type useFormValidatorObject = { validator: useFormValidatorMethod, message?: string, } export type useFormValidator = useFormValidatorMethod | useFormValidatorObject export const HTMLInputExtractor: useFormExtractor = (e: React.FormEvent) => e.currentTarget.value export const HTMLCheckboxExtractor: useFormExtractor = (e: React.FormEvent) => e.currentTarget.checked export const useForm = (init: T, validators: Partial = {}, options: useFormOptions = {}) => { const [form, setForm] = useState(init) const [errors, setErrors] = useState>({}) const _set = (key: keyof T, value: any) => { setForm({ ...form, [key]: value, }) } const _validateAll = async (value: any, validator: useFormValidatorMethod): Promise => { if (validator.constructor.name === 'Function' || validator.constructor.name === 'AsyncFunction') return (validator as useFormValidatorFunction)(value) else if (validator.constructor.name === 'RegExp') return (validator as RegExp).test(value) else return false } const _getValidatorMessage = (key: keyof T): string => { // @ts-ignore if (validators[key] && validators[key].message) return validators[key].message else return `Error in: ${key}` } const _validate = (key: keyof T, value: any) => { const validator: useFormValidator | undefined = validators[key] if (!validator) return // @ts-ignore _validateAll(value, validator.constructor.name === 'Object' ? (validator as useFormValidatorObject).validator : validator) .then((valid: boolean) => { setErrors({ ...errors, [key]: valid ? undefined : _getValidatorMessage(key), }) }) } const update = (key: keyof T, extractor = options.extractor) => (value: any) => { const extracted = extractor ? extractor(value) : HTMLInputExtractor(value) _set(key, extracted) _validate(key, extracted) } const auto = (key: keyof T, opts: useFormAutoOptions = {}) => ({ [opts.getter || 'onChange']: update(key, opts.extractor), [opts.setter || 'value']: form[key] as any, }) return { form, update, auto, errors } }