2019-09-28 17:00:49 +00:00
|
|
|
import React, { useState, useEffect } from 'react'
|
2019-09-26 19:13:27 +00:00
|
|
|
|
|
|
|
export type useFormExtractor = (from: any) => any
|
|
|
|
|
|
|
|
export type useFormOptions = {
|
2019-10-19 06:36:50 +00:00
|
|
|
extractor?: useFormExtractor
|
|
|
|
getter?: string
|
|
|
|
setter?: string
|
2019-09-26 19:13:27 +00:00
|
|
|
}
|
|
|
|
|
2019-10-19 18:15:54 +00:00
|
|
|
export type useFormValidatorFunctionReturn = boolean | string
|
|
|
|
export type useFormValidatorFunction = (s: any) => useFormValidatorFunctionReturn | Promise<useFormValidatorFunctionReturn>
|
2019-09-26 19:13:27 +00:00
|
|
|
export type useFormValidatorMethod = useFormValidatorFunction | RegExp
|
|
|
|
|
|
|
|
export type useFormValidatorObject = {
|
2019-10-19 06:36:50 +00:00
|
|
|
validator: useFormValidatorMethod
|
|
|
|
message?: string
|
2019-09-26 19:13:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export type useFormValidator = useFormValidatorMethod | useFormValidatorObject
|
|
|
|
|
2019-09-28 17:00:49 +00:00
|
|
|
export type useFormValidatorParameter = useFormValidator | useFormValidator[]
|
|
|
|
|
2019-09-26 19:13:27 +00:00
|
|
|
export const HTMLInputExtractor: useFormExtractor = (e: React.FormEvent<HTMLInputElement>) => e.currentTarget.value
|
|
|
|
export const HTMLCheckboxExtractor: useFormExtractor = (e: React.FormEvent<HTMLInputElement>) => e.currentTarget.checked
|
|
|
|
|
2019-09-28 17:00:49 +00:00
|
|
|
function isFormValidatorObject(validator: useFormValidatorMethod | useFormValidatorObject): validator is useFormValidatorObject {
|
2019-10-19 06:36:50 +00:00
|
|
|
return validator.constructor.name === 'Object'
|
2019-09-28 17:00:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const defaultErrorMessage = (key: any) => `Error in ${key}`
|
|
|
|
|
2019-10-19 14:57:26 +00:00
|
|
|
export const useForm = <T extends object, U extends { [key in keyof T]: useFormValidatorParameter }, E extends { [key in keyof U]?: string }>(
|
|
|
|
init: T,
|
|
|
|
validators: Partial<U> = {},
|
|
|
|
options: useFormOptions = {}
|
|
|
|
) => {
|
2019-10-19 06:36:50 +00:00
|
|
|
const [form, setForm] = useState<T>(init)
|
|
|
|
|
|
|
|
const [errors, setErrors] = useState<Partial<E>>({})
|
|
|
|
const [isValid, setIsValid] = useState(true)
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setIsValid(!Object.values(errors).reduce((acc, cur) => acc || cur !== undefined, false))
|
|
|
|
}, [errors])
|
|
|
|
|
2019-10-19 14:57:26 +00:00
|
|
|
const _set = <A extends keyof T>(key: A, value: T[A]) => {
|
2019-10-19 06:36:50 +00:00
|
|
|
setForm({
|
|
|
|
...form,
|
|
|
|
[key]: value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-19 18:15:54 +00:00
|
|
|
const _validateAll = async (value: any, object: useFormValidator): Promise<useFormValidatorFunctionReturn> => {
|
2019-10-19 06:36:50 +00:00
|
|
|
const validator = isFormValidatorObject(object) ? object.validator : object
|
|
|
|
|
|
|
|
if (validator.constructor.name === 'Function') return (validator as useFormValidatorFunction)(value)
|
|
|
|
else if (validator.constructor.name === 'AsyncFunction') return await (validator as useFormValidatorFunction)(value)
|
|
|
|
else if (validator.constructor.name === 'RegExp') return (validator as RegExp).test(value)
|
|
|
|
else return false
|
|
|
|
}
|
|
|
|
|
2019-10-19 18:15:54 +00:00
|
|
|
const _getErrorMessage = (result: useFormValidatorFunctionReturn, key: keyof T, validator: useFormValidatorMethod | useFormValidatorObject) =>
|
|
|
|
result === true ? undefined : result.constructor.name === 'String' ? result : isFormValidatorObject(validator) && validator.message ? validator.message : defaultErrorMessage(key)
|
|
|
|
|
2019-10-19 06:36:50 +00:00
|
|
|
const _validate = (key: keyof T, value: any) => {
|
|
|
|
const validator: useFormValidatorParameter | undefined = validators[key]
|
|
|
|
if (!validator) return
|
|
|
|
|
|
|
|
if (Array.isArray(validator)) {
|
2019-10-19 18:15:54 +00:00
|
|
|
Promise.all(validator.map(v => _validateAll(value, v))).then(results => {
|
|
|
|
const i = results.findIndex(result => result !== true)
|
2019-10-19 06:36:50 +00:00
|
|
|
setErrors({
|
|
|
|
...errors,
|
2019-10-19 18:15:54 +00:00
|
|
|
[key]: i === -1 ? undefined : _getErrorMessage(results[i], key, validator[i]),
|
2019-10-19 06:36:50 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
} else {
|
2019-10-19 18:15:54 +00:00
|
|
|
_validateAll(value, validator).then(result => {
|
2019-10-19 06:36:50 +00:00
|
|
|
setErrors({
|
|
|
|
...errors,
|
2019-10-19 18:15:54 +00:00
|
|
|
[key]: _getErrorMessage(result, key, validator),
|
2019-10-19 06:36:50 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-19 14:57:26 +00:00
|
|
|
const update = <A extends keyof T>(key: A, extractor = options.extractor) => (value: T[A]) => {
|
2019-10-19 06:36:50 +00:00
|
|
|
const extracted = extractor ? extractor(value) : HTMLInputExtractor(value)
|
|
|
|
_set(key, extracted)
|
|
|
|
_validate(key, extracted)
|
|
|
|
}
|
|
|
|
|
2019-10-19 06:45:14 +00:00
|
|
|
const field = (key: keyof T, opts: useFormOptions = {}) => ({
|
2019-10-19 06:36:50 +00:00
|
|
|
[opts.getter || options.getter || 'onChange']: update(key, opts.extractor),
|
|
|
|
[opts.setter || options.setter || 'value']: form[key] as any,
|
|
|
|
})
|
|
|
|
|
2019-10-19 14:57:26 +00:00
|
|
|
return { form, update, field, errors, isValid, setForm, setErrors, setField: _set }
|
2019-10-19 06:36:50 +00:00
|
|
|
}
|