diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-03 19:34:16 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:15:27 +0100 |
| commit | ddd45e29745b73e7fe1684e197dcff598b375644 (patch) | |
| tree | 8bf01305b5c0d163c52a7dce747ed7a4a4650acb /src/utils/hooks/use-form/use-form.ts | |
| parent | 5d3e8a4d0c2ce2ad8f22df857ab3ce54fcfc38ac (diff) | |
feat(hooks): add an useForm hook
* add two "sub"-hooks: useFormValues and useFormSubmit (that
can be used independently)
* handle initial data
* handle custom submit callback
* handle data validation
* handle submit status
Diffstat (limited to 'src/utils/hooks/use-form/use-form.ts')
| -rw-r--r-- | src/utils/hooks/use-form/use-form.ts | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/utils/hooks/use-form/use-form.ts b/src/utils/hooks/use-form/use-form.ts new file mode 100644 index 0000000..492f820 --- /dev/null +++ b/src/utils/hooks/use-form/use-form.ts @@ -0,0 +1,134 @@ +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<T> = { + /** + * The error message. + */ + error: string; + /** + * A function to validate the field value. + */ + validator: DataValidator<T>; +}; + +export type FormValidations<T> = { + [K in keyof T]: FormFieldValidation<T[K]>; +}; + +export type FormValidationErrors<T> = { + [K in keyof T]?: string; +}; + +export type UseFormConfig<T> = { + /** + * The initial fields values. + */ + initialValues: T; + /** + * A function to handle submit. + */ + submitHandler?: FormSubmitHandler<T>; + /** + * An object with validator and error message to validate each field. + */ + validations?: Partial<FormValidations<T>>; +}; + +export type UseFormReturn<T extends Record<string, unknown>> = + UseFormValuesReturn<T> & + UseFormSubmitReturn & { + /** + * The validation error for each field. + */ + validationErrors: Nullable<FormValidationErrors<T>>; + }; + +/** + * React hook to manage forms. + * + * @template {object} T - The object keys should match the fields name. + * @param {UseFormConfig<T>} config - The config. + * @returns {UseFormReturn<T>} The values, validations errors and form methods. + */ +export const useForm = <T extends Record<string, unknown>>({ + initialValues, + submitHandler, + validations, +}: UseFormConfig<T>): UseFormReturn<T> => { + const { values, reset, update } = useFormValues(initialValues); + const [validationErrors, setValidationErrors] = + useState<Nullable<FormValidationErrors<T>>>(null); + + const areValuesValid = useCallback(async () => { + if (!validations) return true; + + const keys = Object.keys(values) as Extract<keyof T, string>[]; + 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<T> = 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, + }; +}; |
