import React, {useRef} from 'react'; import {isEmailValid, isEmptyObject, isRequired, maxLength, minLength} from "./helpers/validateFunctions"; import {Settings} from "./types/Types"; import {defaultValidation, getEqualValidationValues, objectsEqual} from "./helpers/hookHelpers"; interface Target { name: string, value: any } export default function useOnChange( settings: Settings, deps: any[] = [] ): [ T | null, (target: Target | Target[]) => void, { [key: string]: string }, boolean | null, () => void, boolean, (value: boolean) => void ] { const initialState = settings?.initialState || null; const [saving, setSaving] = React.useState(false); const [toggledCanSave, setToggleCanSave] = React.useState(null); const [data, setData] = React.useState(initialState); const [errors, setErrors] = React.useState<{ [key: string]: string }>({}); const canSaveErrors: { [key: string]: boolean } = {}; React.useEffect(() => { setData({...initialState}); setErrors({}); let formFields = settings?.validators && Object.keys(settings.validators); if (formFields?.length) { formFields?.forEach((field) => { if (initialState?.[field]?.length) { validateData(field, initialState?.[field]); } }); } }, [saving, ...deps]); // This part checking existing fields and fills errors based on data inside React.useEffect(() => checkErrors(), []); // This part makes initial data validation for default logic and cantSaveUnchaged const checkErrors = () => { const validators = settings?.validators; const config = settings?.canSaveConfig; // If no validators are provided, we skip the error initialization if (!validators) return; const formFields = Object.keys(validators); const initialErrors: { [key: string]: string } = {}; formFields.forEach((field) => { const fieldValue = initialState?.[field]; const validate = validateField(field, fieldValue); // Handle cantSaveUnchanged logic and validation for fields in validators if (config?.cantSaveUnchanged && fieldValue?.length) { initialErrors[field] = ''; } else if ((fieldValue?.length && validators?.[field]) || (fieldValue?.length === 0 && validators?.[field] && !validate)) { initialErrors[field] = validate; } }); setErrors(initialErrors); }; const canSave = React.useMemo(() => { const canSave = settings?.canSaveConfig?.canSave; const canSaveConfig = settings?.canSaveConfig; // We get only those fiends which added in validators array const initialStateToCheck = getEqualValidationValues(initialState, settings?.validators); const dataToCheck = getEqualValidationValues(data, settings?.validators); if (!canSaveConfig) return null; if (settings?.validators && (isEmptyObject(settings?.validators) && isEmptyObject(canSaveConfig)) || (!!canSaveConfig && canSave === false)) return null; // If nothing we just checking if no error returns and fields not empty if (!canSaveConfig?.defaultValidation) canSaveErrors['defaultValidation'] = defaultValidation(errors, settings); if (!!canSaveConfig && canSaveConfig?.cantSaveUnchanged && !!data && !!initialState) canSaveErrors['equalDataValidation'] = !objectsEqual(initialStateToCheck, dataToCheck); // If function exist in settings if (!!canSaveConfig && typeof canSaveConfig?.validationFunction === 'function') canSaveErrors['userValidation'] = canSaveConfig?.validationFunction(data, errors, initialState); return (toggledCanSave || Object.values(canSaveErrors).every(item => !!item)); }, [data, errors, settings.canSaveConfig]); const toggleCanSave = (value: boolean) => setToggleCanSave(value); // Supports single or multi value const onChange = (target: Target | Target[]) => { // Get updated value const updatedData = Array.isArray(target) ? target.reduce((acc, { name, value }) => ({ ...acc, [name]: value }), {}) : { [target.name]: target.value }; // Merge new and old state const newData = { ...data, ...updatedData }; // Set data without validation? setData(newData); // And then we validate fields only related to updated data? so how hookWillKnow about updated fields Object.keys(newData).forEach(fieldName => { const value = newData?.[fieldName]; validateData(fieldName, value); }); }; const runValidators = (validators: Function[], fieldValue: any, data: T | null) => { // Validate if validators exist if (validators) { for (let validate of validators) { const validationError = validate(fieldValue, data); if (validationError) return validationError; } } // If no validators just toggle return ''; }; // ToDo this part shows only fields listed in validators const validateField = (fieldName: string, fieldValue: any) => { // Get validators if they exist const validators = settings.validators?.[fieldName]; // If validators exist we validate in other case return empty string to toggle canSave return runValidators(validators, fieldValue, data); }; const validateData = (fieldName: string, fieldValue: any) => { // Validate error should be error string or empty string const validationError = validateField(fieldName, fieldValue); setErrors(prevErrors => ({ ...prevErrors, [fieldName]: validationError })); }; const cleanUp = React.useCallback(() => { if (!!settings?.config?.shouldCleanupOnSave) { setData(null); setErrors({}); } }, [saving, data, initialState]); const requestFunction = React.useCallback(() => { let {config} = settings; let onFinalSaved = false; if (config && !isEmptyObject(config) && (!!canSave || canSave === null)) { setSaving(true); if (typeof config?.fetchMethod === 'function') { config.fetchMethod(data).then(() => { if (typeof config?.onSuccessAction === 'function') config.onSuccessAction(data); }).finally(() => { onFinalSaved = true; setSaving(false); }); if (onFinalSaved) cleanUp(); onFinalSaved = false; } } }, [saving, data, initialState]); return [ data, onChange, errors, canSave, requestFunction, saving, toggleCanSave ]; };