UNPKG

ra-core

Version:

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

155 lines (142 loc) 4.67 kB
import { BaseSyntheticEvent, useCallback, useEffect, useMemo, useRef, } from 'react'; import { FieldValues, SubmitHandler, useForm, UseFormProps, } from 'react-hook-form'; import merge from 'lodash/merge'; import { RaRecord } from '../types'; import { SaveHandler, useRecordContext, useSaveContext } from '../controller'; import getFormInitialValues from './getFormInitialValues'; import { getSimpleValidationResolver, ValidateForm, } from './validation/getSimpleValidationResolver'; import { setSubmissionErrors } from './validation/setSubmissionErrors'; import { useNotifyIsFormInvalid } from './validation/useNotifyIsFormInvalid'; import { sanitizeEmptyValues as sanitizeValues } from './sanitizeEmptyValues'; import { useRecordFromLocation } from './useRecordFromLocation'; /** * 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 = <RecordType = any>( props: UseAugmentedFormProps<RecordType> ) => { const { criteriaMode = 'firstError', defaultValues, formRootPathname, resolver, reValidateMode = 'onChange', onSubmit, sanitizeEmptyValues, validate, disableInvalidFormNotification, ...rest } = props; const saveContext = useSaveContext(); const record = useRecordContext(props); 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, !disableInvalidFormNotification); const recordFromLocation = useRecordFromLocation(); const recordFromLocationApplied = useRef(false); const { reset } = form; useEffect(() => { if (recordFromLocation && !recordFromLocationApplied.current) { reset(merge({}, defaultValuesIncludingRecord, recordFromLocation), { keepDefaultValues: true, }); recordFromLocationApplied.current = true; } }, [defaultValuesIncludingRecord, recordFromLocation, reset]); // 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<RecordType = any> = UseFormOwnProps<RecordType> & Omit<UseFormProps, 'onSubmit'> & { validate?: ValidateForm; }; export interface UseFormOwnProps<RecordType = any> { defaultValues?: any; formRootPathname?: string; record?: Partial<RaRecord>; onSubmit?: SubmitHandler<FieldValues> | SaveHandler<RecordType>; sanitizeEmptyValues?: boolean; disableInvalidFormNotification?: boolean; }