diff --git a/examples/errorsAndValidation.tsx b/examples/errorsAndValidation.tsx index 476ebf6..28b76c4 100644 --- a/examples/errorsAndValidation.tsx +++ b/examples/errorsAndValidation.tsx @@ -7,10 +7,24 @@ const Index: React.FC = () => { const { auto, form, errors } = useForm({ username: '', + email: '', password: '' }, { username: value => value.length > 3, - password: /[\d]{1,}/ + email: { + validator: /@/, + message: 'Must contain an @', + }, + password: [ + { + validator: /[A-Z]/, + message: 'Must contain an uppercase letter' + }, + { + validator: /[\d]/, + message: 'Must contain a digit' + }, + ] }) return ( @@ -21,8 +35,11 @@ const Index: React.FC = () => { {errors.username && 'Must be longer than 3'} + + {errors.email} + - {errors.password && 'Must contain a number'} + {errors.password} ) diff --git a/lib/index.ts b/lib/index.ts index 6f2a977..60de332 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' @@ -20,13 +20,26 @@ export type useFormValidatorObject = { export type useFormValidator = useFormValidatorMethod | useFormValidatorObject +export type useFormValidatorParameter = useFormValidator | useFormValidator[] + 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 = {}) => { +function isFormValidatorObject(validator: useFormValidatorMethod | useFormValidatorObject): validator is useFormValidatorObject { + return validator.constructor.name === 'Object' +} + +const defaultErrorMessage = (key: any) => `Error in ${key}` + +export const useForm = (init: T, validators: Partial = {}, options: useFormOptions = {}) => { const [form, setForm] = useState(init) const [errors, setErrors] = useState>({}) + const [isValid, setIsValid] = useState(true); + + useEffect(() => { + setIsValid(!Object.values(errors).reduce((acc, cur) => acc || cur !== undefined, false)) + }, [errors]) const _set = (key: keyof T, value: any) => { setForm({ @@ -35,34 +48,48 @@ export const useForm = => { - if (validator.constructor.name === 'Function' || validator.constructor.name === 'AsyncFunction') + const _validateAll = async (value: any, object: useFormValidator): Promise => { + 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 _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] + const validator: useFormValidatorParameter | 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), + if (Array.isArray(validator)) { + Promise.all(validator.map(v => _validateAll(value, v))) + .then(result => { + const index = result.indexOf(false) + setErrors({ + ...errors, + [key]: index === -1 + ? undefined + : isFormValidatorObject(validator[index]) && (validator[index] as useFormValidatorObject).message + ? (validator[index] as useFormValidatorObject).message + : defaultErrorMessage(key) + }) }) - }) + } else { + _validateAll(value, validator) + .then(valid => { + setErrors({ + ...errors, + [key]: valid + ? undefined + : isFormValidatorObject(validator) && validator.message + ? validator.message + : defaultErrorMessage(key) + }) + }) + } } const update = (key: keyof T, extractor = options.extractor) => (value: any) => { @@ -76,5 +103,5 @@ export const useForm = = ({ error }) => !error const Index: React.FC = () => { - const { auto, form, errors } = useForm({ + const { auto, form, errors, isValid } = useForm({ username: '', password: '', type: 'formhero', awesome: true, }, { - username: /^test/, + username: [ + /^test/, + { + validator: async () => { return true }, + message: 'Digits please', + } + ], password: { validator: /^.{3,}$/, message: 'To short', @@ -26,7 +32,7 @@ const Index: React.FC = () => { const _submit = (e: React.FormEvent) => { e.preventDefault() - console.log(form, errors) + console.log(form, errors, isValid) } return (