import { useCallback, useState } from 'react'; import type { DataValidator, Nullable } from '../../../types'; import { type FormSubmitHandler, useFormSubmit, type UseFormSubmitReturn, } from './use-form-submit'; import { type UseFormValuesReturn, useFormValues } from './use-form-values'; export type FormFieldValidation = { /** * The error message. */ error: string; /** * A function to validate the field value. */ validator: DataValidator; }; export type FormValidations = { [K in keyof T]: FormFieldValidation; }; export type FormValidationErrors = { [K in keyof T]?: string; }; export type UseFormConfig = { /** * The initial fields values. */ initialValues: T; /** * A function to handle submit. */ submitHandler?: FormSubmitHandler; /** * An object with validator and error message to validate each field. */ validations?: Partial>; }; export type UseFormReturn> = UseFormValuesReturn & UseFormSubmitReturn & { /** * The validation error for each field. */ validationErrors: Nullable>; }; /** * React hook to manage forms. * * @template {object} T - The object keys should match the fields name. * @param {UseFormConfig} config - The config. * @returns {UseFormReturn} The values, validations errors and form methods. */ export const useForm = >({ initialValues, submitHandler, validations, }: UseFormConfig): UseFormReturn => { const { values, reset, update } = useFormValues(initialValues); const [validationErrors, setValidationErrors] = useState>>(null); const areValuesValid = useCallback(async () => { if (!validations) return true; const keys = Object.keys(values) as Extract[]; const validationPromises = keys.map(async (key) => { const value = values[key]; const field = validations[key]; if (!field) return true; const isValidField = await field.validator(value); if (!isValidField) setValidationErrors((prevErr) => { const newErrors: FormValidationErrors = prevErr ? { ...prevErr } : {}; return { ...newErrors, [key]: field.error, }; }); return isValidField; }); const awaitedValidation = await Promise.all(validationPromises); return awaitedValidation.every((isValid) => isValid); }, [validations, values]); const handleSubmit = useCallback(async () => { setValidationErrors(null); const isValid = await areValuesValid(); if (isValid && submitHandler) return submitHandler(values); return { validator: () => isValid, messages: { error: 'Has invalid values', }, }; }, [areValuesValid, submitHandler, values]); const handleSuccess = useCallback(() => { reset(); }, [reset]); const { messages, submit, submitStatus } = useFormSubmit(values, { onSuccess: handleSuccess, submit: handleSubmit, }); return { messages, reset, submit, submitStatus, update, values, validationErrors, }; };