import React, {FormEvent, useCallback, useState} from 'react'; import _ from 'lodash'; import {TForm, TFormError, TFormFields, TFormErrors} from '../types'; const Form = ({ className, action, method = 'GET', initialValues, onChange, onValidate, onValid, onSubmit, onError, children }: TForm): JSX.Element => { const [isSubmitting, setSubmitting] = useState(false); const [isValid, setValid] = useState(false); const [isSubmitted, setSubmitted] = useState(false); const [fields, setFields] = useState( _.mapValues(initialValues, value => ({ value: value, error: false, touched: false })) ); const [error, setError] = useState(null); const handleValidate = useCallback( (targetFields = fields, {ignoreTouched} = {}): void => { if (_.isFunction(onValidate)) { onValidate({fields: targetFields, isSubmitted, isValid}) .then((validatedFields: TFormErrors) => { if (_.isObject(validatedFields)) { _.forIn(targetFields, ({value, touched}, name) => { targetFields[name] = { value, touched, error: ignoreTouched ? touched && Boolean(validatedFields[name]) : Boolean(validatedFields[name]) }; }); } setFields(targetFields); if ( _.isEmpty(validatedFields) || !_.every(validatedFields, Boolean) ) { !isValid && setValid(true); _.isFunction(onValid) && onValid({fields: targetFields, isSubmitted, isValid}); } else { isValid && setValid(false); } }) .catch(_.noop); } else { setValid(true); _.isFunction(onValid) && onValid({fields: targetFields, isSubmitted, isValid}); setFields( _.mapValues(targetFields, ({value, touched}) => ({ value, touched, error: false })) ); } }, [fields, onValidate, isSubmitted, isValid, onValid] ); const handleChange = useCallback( (name: string, value: string | undefined): void => { setError(null); const newFields = { ...fields, [name]: { value, touched: value !== initialValues[name], error: fields[name].error } }; handleValidate(newFields, {ignoreTouched: true}); }, [fields, initialValues, handleValidate] ); const updateForm = useCallback((): void => { handleValidate(); if (_.isFunction(onChange)) { onChange({fields, isSubmitted, isValid}) .then(newFields => { if (newFields && !_.isEmpty(newFields)) { setFields(newFields); } }) .catch(_.noop); } }, [fields, isValid, isSubmitted, onChange, handleValidate]); const handleReset = useCallback((): void => { setFields( _.mapValues(initialValues, value => ({ value: value, error: false, touched: false })) ); setSubmitted(false); setError(null); }, [initialValues]); const handleSubmit = useCallback( (event: FormEvent): void => { event.preventDefault(); setSubmitting(true); _.isFunction(onSubmit) && onSubmit({fields, isSubmitted, isValid, resetForm: handleReset}) .then(() => { setSubmitted(true); }) .catch(error => { setError(error); _.isFunction(onError) && onError({ fields, isSubmitted, isValid, error, resetForm: handleReset }); }) .finally(() => setSubmitting(false)); }, [onSubmit, fields, isSubmitted, isValid, handleReset, onError] ); const renderChildren = useCallback( () => children({ fields, isValid, isSubmitted, isSubmitting, updateForm, formError: error, onChange: handleChange, submitForm: handleSubmit, resetForm: handleReset }), [ isValid, isSubmitted, isSubmitting, children, fields, error, handleReset, handleChange, updateForm, handleSubmit ] ); return (
{renderChildren()}
); }; export default Form;