UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

142 lines (128 loc) 4.07 kB
import { BaseSyntheticEvent, useCallback, useMemo, useRef } from 'react'; import { FieldValues, SubmitHandler, useForm, UseFormProps, } from 'react-hook-form'; import { RaRecord } from '../types'; import { useSaveContext } from '../controller'; import { useRecordContext } from '../controller'; import getFormInitialValues from './getFormInitialValues'; import { getSimpleValidationResolver, ValidateForm, } from './getSimpleValidationResolver'; import { setSubmissionErrors } from './setSubmissionErrors'; import { useNotifyIsFormInvalid } from './useNotifyIsFormInvalid'; import { useWarnWhenUnsavedChanges } from './useWarnWhenUnsavedChanges'; import { sanitizeEmptyValues as sanitizeValues } from './sanitizeEmptyValues'; /** * Wrapper around react-hook-form's useForm * * This hook adds the following features to react-hook-form's useForm: * * - form initialization based on RecordContext * - validation based on a validate function * - sanitization of empty values * - notification on invalid form * - stop form submission event propagation */ export const useAugmentedForm = (props: UseAugmentedFormProps) => { const { criteriaMode = 'firstError', defaultValues, formRootPathname, resolver, reValidateMode = 'onChange', onSubmit, sanitizeEmptyValues, warnWhenUnsavedChanges, validate, ...rest } = props; const record = useRecordContext(props); const saveContext = useSaveContext(); const defaultValuesIncludingRecord = useMemo( () => getFormInitialValues(defaultValues, record), // eslint-disable-next-line [ // eslint-disable-next-line JSON.stringify({ defaultValues: typeof defaultValues === 'function' ? 'function' : defaultValues, record, }), ] ); const finalResolver = resolver ? resolver : validate ? getSimpleValidationResolver(validate) : undefined; const form = useForm({ criteriaMode, values: defaultValuesIncludingRecord, reValidateMode, resolver: finalResolver, ...rest, }); const formRef = useRef(form); // notify on invalid form useNotifyIsFormInvalid(form.control); // warn when unsaved change useWarnWhenUnsavedChanges( Boolean(warnWhenUnsavedChanges), formRootPathname, form.control ); // submit callbacks const handleSubmit = useCallback( async (values, event) => { let errors; const finalValues = sanitizeEmptyValues ? sanitizeValues(values, record) : values; if (onSubmit) { errors = await onSubmit(finalValues, event); } if (onSubmit == null && saveContext?.save) { errors = await saveContext.save(finalValues, event); } if (errors != null) { setSubmissionErrors(errors, formRef.current.setError); } }, [onSubmit, saveContext, sanitizeEmptyValues, record] ); const formHandleSubmit = useCallback( (event: BaseSyntheticEvent) => { if (!event.defaultPrevented) { // Prevent outer forms to receive the event event.stopPropagation(); form.handleSubmit(handleSubmit)(event); } return; }, [form, handleSubmit] ); return { form, handleSubmit, formHandleSubmit, }; }; export type UseAugmentedFormProps = UseFormOwnProps & Omit<UseFormProps, 'onSubmit'> & { validate?: ValidateForm; }; export interface UseFormOwnProps { defaultValues?: any; formRootPathname?: string; record?: Partial<RaRecord>; onSubmit?: SubmitHandler<FieldValues>; warnWhenUnsavedChanges?: boolean; sanitizeEmptyValues?: boolean; }