saleor-dashboard/src/hooks/useForm.ts
Patryk Andrzejewski 1d2eeb7592
Strict mode plugin (#3778)
* Stric mode plugin

* Update command
2023-06-21 11:28:00 +02:00

231 lines
5.1 KiB
TypeScript

// @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<TData = any> {
target: {
name: string;
value: TData;
};
}
export type SubmitPromise<TData = any> = Promise<TData>;
export type FormChange<T = any> = (
event: ChangeEvent<T>,
cb?: () => void,
) => void;
export type FormErrors<T> = {
[field in keyof T]?: string | React.ReactNode;
};
export interface UseFormOpts<T> {
confirmLeave: boolean;
formId?: FormId;
checkIfSaveIsDisabled?: CheckIfSaveIsDisabledFnType<T>;
disabled?: boolean;
mergeData?: boolean;
}
export interface UseFormResult<TData>
extends CommonUseFormResult<TData>,
Pick<UseExitFormDialogResult, "formId"> {
reset: () => void;
set: (data: Partial<TData>) => void;
triggerChange: () => void;
handleChange: FormChange;
toggleValue: FormChange;
errors: FormErrors<TData>;
setError: (name: keyof TData, error: string | React.ReactNode) => void;
clearErrors: (name?: keyof TData | Array<keyof TData>) => void;
setIsSubmitDisabled: (value: boolean) => void;
}
export interface CommonUseFormResult<TData> {
data: TData;
change: FormChange;
submit: (dataOrEvent?: any) => SubmitPromise<any[]>;
isSaveDisabled?: boolean;
}
export interface CommonUseFormResultWithHandlers<TData, THandlers>
extends CommonUseFormResult<TData> {
handlers: THandlers;
}
type FormData = Record<string, any | any[]>;
function merge<T extends FormData>(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<T extends FormData, TErrors>(
initialData: T,
onSubmit?: (data: T) => SubmitPromise<TErrors[]> | void,
opts: UseFormOpts<T> = { confirmLeave: false, formId: undefined },
): UseFormResult<T> {
const {
confirmLeave,
formId: propsFormId,
checkIfSaveIsDisabled,
disabled,
mergeData = true,
} = opts;
const [errors, setErrors] = useState<FormErrors<T>>({});
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<T>) {
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<keyof T>) => {
if (!field) {
setErrors({});
} else {
setErrors(errors =>
omit<FormErrors<T>>(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;