diff --git a/striker-ui/hooks/useFormikUtils.ts b/striker-ui/hooks/useFormikUtils.ts new file mode 100644 index 00000000..29a4c69e --- /dev/null +++ b/striker-ui/hooks/useFormikUtils.ts @@ -0,0 +1,96 @@ +import { OutlinedInputProps } from '@mui/material'; +import { FormikValues, useFormik } from 'formik'; +import { useCallback, useMemo } from 'react'; + +import debounce from '../lib/debounce'; +import getFormikErrorMessages from '../lib/getFormikErrorMessages'; + +type UseFormik = typeof useFormik; + +type Formik = ReturnType>; + +type FormikChangeHandler = + Formik['handleChange']; + +const useFormikUtils = ( + ...formikArgs: Parameters> +): { + disableAutocomplete: ( + overwrite?: Partial, + ) => OutlinedInputProps; + disabledSubmit: boolean; + formik: Formik; + formikErrors: Messages; + handleChange: FormikChangeHandler; +} => { + const [formikConfig, ...restFormikArgs] = formikArgs; + + const formik = useFormik({ ...formikConfig }, ...restFormikArgs); + + const getFieldChanged = useCallback( + (field: string) => { + const parts = field.split('.'); + + const traverse = (values: Tree): boolean => + parts.reduce((previous, part) => { + if (!(part in values)) { + return false; + } + + const value = values[part]; + + if (value !== null && typeof value === 'object') { + return traverse(value as Tree); + } + + return value === formik.initialValues[part]; + }, false); + + return traverse(formik.values); + }, + [formik.initialValues, formik.values], + ); + + const disableAutocomplete = useCallback( + (overwrite?: Partial): OutlinedInputProps => ({ + readOnly: true, + onFocus: (event) => { + event.target.readOnly = false; + }, + ...overwrite, + }), + [], + ); + + const debounceHandleChange = useMemo( + () => debounce(formik.handleChange), + [formik.handleChange], + ); + + const disabledSubmit = useMemo( + () => + !formik.dirty || + !formik.isValid || + formik.isValidating || + formik.isSubmitting, + [formik.dirty, formik.isSubmitting, formik.isValid, formik.isValidating], + ); + + const formikErrors = useMemo( + () => + getFormikErrorMessages(formik.errors, { + skip: (field) => !getFieldChanged(field), + }), + [formik.errors, getFieldChanged], + ); + + return { + disableAutocomplete, + disabledSubmit, + formik, + formikErrors, + handleChange: debounceHandleChange, + }; +}; + +export default useFormikUtils;