import { useState, useEffect, SetStateAction, useCallback } from 'react'; import { FormState } from './FormProvider.js'; export type FormValidateError = Partial>; export type FormValidateFn = ( value: T, setError: (error: FormValidateError | null) => void ) => void; export type UseFormProps = { initial: T; validate?: FormValidateFn; }; const getDiffStateProps = >(a: T, b: T) => { const keys = Object.keys(a); const diff: Record = {}; for (const key of keys) { if (a[key] !== b[key]) { diff[key] = true; } } return diff; }; const useForm = , E = string>({ initial, validate, }: UseFormProps): FormState => { const [isDirty, setIsDirty] = useState(false); const [hasSubmitted, setHasSubmitted] = useState(false); const [dirtyFields, setDirtyFields] = useState< Partial> >({}); const [state, setState] = useState(initial); const [error, setError] = useState>({}); const getIsDirty = (field?: keyof T) => { if (!field) { return Object.values(dirtyFields).some(Boolean); } return isDirty || !!dirtyFields[field]; }; const reset = useCallback(() => { setState(initial); setIsDirty(false); setHasSubmitted(false); setDirtyFields({}); }, [initial]); const handleSetState = useCallback((setStateAction: SetStateAction) => { setState((prevState) => { const nextState = typeof setStateAction === 'function' ? (setStateAction as any)(prevState) : setStateAction; const dirtyFields = getDiffStateProps(nextState, prevState); setDirtyFields((prevDirtyFields) => ({ ...prevDirtyFields, ...dirtyFields, })); return nextState; }); }, []); const handleSetError = useCallback( (error: FormValidateError | null) => { if (error) { setError(error); } else { setError({}); } }, [] ); const setDirty = (field: keyof T) => { setDirtyFields((f) => ({ ...f, [field]: true })); }; const setSubmitted = () => { if (validate) { validate(state, handleSetError); } setIsDirty(true); setHasSubmitted(true); }; useEffect(() => { if (initial) { reset(); } }, [initial, reset]); useEffect(() => { if (validate) { validate(state, handleSetError); } else { setError({}); } }, [handleSetError, validate, setError, state]); return { error, getIsDirty, hasError: Object.keys(error).length > 0, hasSubmitted, state, setDirty, setError: handleSetError, setState: handleSetState, setSubmitted, reset, }; }; export default useForm;