// @ts-strict-ignore import { CheckIfSaveIsDisabledFnType, FormId, useExitFormDialog, UseExitFormDialogResult, } from "@dashboard/components/Form"; import useHandleFormSubmit from "@dashboard/hooks/useHandleFormSubmit"; import { toggle } from "@dashboard/utils/lists"; import isEqual from "lodash/isEqual"; import omit from "lodash/omit"; import React, { useEffect, useState } from "react"; import useStateFromProps from "./useStateFromProps"; export interface ChangeEvent { target: { name: string; value: TData; }; } export type SubmitPromise = Promise; export type FormChange = ( event: ChangeEvent, cb?: () => void, ) => void; export type FormErrors = { [field in keyof T]?: string | React.ReactNode; }; export interface UseFormOpts { confirmLeave: boolean; formId?: FormId; checkIfSaveIsDisabled?: CheckIfSaveIsDisabledFnType; disabled?: boolean; mergeData?: boolean; } export interface UseFormResult extends CommonUseFormResult, Pick { reset: () => void; set: (data: Partial) => void; triggerChange: () => void; handleChange: FormChange; toggleValue: FormChange; errors: FormErrors; setError: (name: keyof TData, error: string | React.ReactNode) => void; clearErrors: (name?: keyof TData | Array) => void; setIsSubmitDisabled: (value: boolean) => void; } export interface CommonUseFormResult { data: TData; change: FormChange; submit: (dataOrEvent?: any) => SubmitPromise; isSaveDisabled?: boolean; } export interface CommonUseFormResultWithHandlers extends CommonUseFormResult { handlers: THandlers; } type FormData = Record; function merge(prevData: T, prevState: T, data: T): T { return Object.keys(prevState).reduce( (acc, key) => { if (!isEqual(data[key], prevData[key])) { acc[key as keyof T] = data[key]; } return acc; }, { ...prevState }, ); } function useForm( initialData: T, onSubmit?: (data: T) => SubmitPromise | void, opts: UseFormOpts = { confirmLeave: false, formId: undefined }, ): UseFormResult { const { confirmLeave, formId: propsFormId, checkIfSaveIsDisabled, disabled, mergeData = true, } = opts; const [errors, setErrors] = useState>({}); const [data, setData] = useStateFromProps(initialData, { mergeFunc: mergeData ? merge : undefined, }); const isSaveDisabled = () => { if (checkIfSaveIsDisabled) { return checkIfSaveIsDisabled(data); } return !!disabled; }; const { setIsDirty: setIsFormDirtyInExitDialog, setExitDialogSubmitRef, setEnableExitDialog, setIsSubmitDisabled, formId, } = useExitFormDialog({ formId: propsFormId, isDisabled: isSaveDisabled(), }); const handleFormSubmit = useHandleFormSubmit({ formId, onSubmit, }); const handleSetChanged = (value: boolean = true) => { if (confirmLeave) { setIsFormDirtyInExitDialog(value); } }; const setExitDialogData = () => { setEnableExitDialog(true); if (!onSubmit) { return; } setExitDialogSubmitRef(submit); }; useEffect(setExitDialogData, [onSubmit, data]); function toggleValue(event: ChangeEvent, cb?: () => void) { const { name, value } = event.target; const field = data[name as keyof T]; if (Array.isArray(field)) { handleSetChanged(true); setData({ ...data, [name]: toggle(value, field, isEqual), }); } if (typeof cb === "function") { cb(); } } const handleChange: FormChange = event => { change(event); handleSetChanged(true); }; function change(event: ChangeEvent) { const { name, value } = event.target; if (!(name in data)) { console.error(`Unknown form field: ${name}`); return; } else { if (data[name] !== value) { handleSetChanged(true); } setData(data => ({ ...data, [name]: value, })); } } function reset() { setData(initialData); } function set(newData: Partial) { setData(data => ({ ...data, ...newData, })); } async function submit() { if (typeof onSubmit === "function" && !Object.keys(errors).length) { const result = handleFormSubmit(data); return result; } } const setError = (field: keyof T, error: string | React.ReactNode) => setErrors(e => ({ ...e, [field]: error })); const clearErrors = (field?: keyof T | Array) => { if (!field) { setErrors({}); } else { setErrors(errors => omit>(errors, Array.isArray(field) ? field : [field]), ); } }; return { formId, setError, errors, change, clearErrors, data, reset, set, submit, toggleValue, handleChange, triggerChange: handleSetChanged, setIsSubmitDisabled, isSaveDisabled: isSaveDisabled(), }; } export default useForm;