import React , { useState , useEffect } from 'react' ; export type useFormExtractor = ( from : any ) => any ; export type useFormOptions = { extractor ? : useFormExtractor ; getter ? : string ; setter ? : string ; } ; export type useFormValidatorFunctionReturn = boolean | string ; export type useFormValidatorFunction = ( s : any ) => useFormValidatorFunctionReturn | Promise < useFormValidatorFunctionReturn > ; export type useFormValidatorMethod = useFormValidatorFunction | RegExp ; export type useFormValidatorObject = { validator : useFormValidatorMethod ; message ? : string ; } ; export type useFormValidator = useFormValidatorMethod | useFormValidatorObject ; export type useFormValidatorParameter = useFormValidator | useFormValidator [ ] ; export const HTMLInputExtractor = ( e ) => e . currentTarget . value ; export const HTMLCheckboxExtractor = ( e ) => e . currentTarget . checked ; function isFormValidatorObject ( validator ) { return validator . constructor . name === 'Object' ; } const defaultErrorMessage = ( key ) => `Error in ${ key }` ; export const useForm = < T extends object , U extends { [ key in keyof T ] : useFormValidatorParameter ; } , E extends { [ key in keyof U ] ? : string ; } > ( init , validators = { } , options = { } ) => { 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 ] ) ; const _set = < A extends keyof T > ( key , value ) => { setForm ( { ... form , [ key ] : value , } ) ; } ; const _validateAll = async ( value , object ) : Promise < useFormValidatorFunctionReturn > => { 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 ; } ; const _getErrorMessage = ( result , key , validator ) => result === true ? undefined : result . constructor . name === 'String' ? result : isFormValidatorObject ( validator ) && validator . message ? validator . message : defaultErrorMessage ( key ) ; const _validate = ( key , value ) => { const validator = validators [ key ] ; if ( ! validator ) return ; if ( Array . isArray ( validator ) ) { Promise . all ( validator . map ( v => _validateAll ( value , v ) ) ) . then ( results => { const i = results . findIndex ( result => result !== true ) ; setErrors ( { ... errors , [ key ] : i === - 1 ? undefined : _getErrorMessage ( results [ i ] , key , validator [ i ] ) , } ) ; } ) ; } else { _validateAll ( value , validator ) . then ( result => { setErrors ( { ... errors , [ key ] : _getErrorMessage ( result , key , validator ) , } ) ; } ) ; } } ; const update = < A extends keyof T > ( key , extractor = options . extractor ) => ( value ) => { const extracted = extractor ? extractor ( value ) : HTMLInputExtractor ( value ) ; _set ( key , extracted ) ; _validate ( key , extracted ) ; } ; const field = ( key , opts = { } ) => ( { [ opts . getter || options . getter || 'onChange' ] : update ( key , opts . extractor ) , [ opts . setter || options . setter || 'value' ] : form [ key ] as any , } ) ; return { form , update , field , errors , isValid , setForm , setErrors , setField : _set } ; } ;