import * as React from 'react' ; import { useRef , useCallback , useEffect , useMemo } from 'react' ; import { Form , FormProps , FormRenderProps } from 'react-final-form' ; import arrayMutators from 'final-form-arrays' ; import { useDispatch } from 'react-redux' ; import useResetSubmitErrors from './useResetSubmitErrors' ; import sanitizeEmptyValues from './sanitizeEmptyValues' ; import getFormInitialValues from './getFormInitialValues' ; import { FormContextValue , Record as RaRecord , OnSuccess , OnFailure , } from '../types' ; import { RedirectionSideEffect } from '../sideEffect' ; import { setAutomaticRefresh } from '../actions' ; import { RecordContextProvider , useRecordContext } from '../controller' ; import { FormContextProvider } from './FormContextProvider' ; import submitErrorsMutators from './submitErrorsMutators' ; import useWarnWhenUnsavedChanges from './useWarnWhenUnsavedChanges' ; example typedef Props prop { Object } initialValues prop { Function } validate prop { Function } save prop { boolean } submitOnEnter prop { string } redirect prop { boolean } sanitizeEmptyValues param { Props } props const FormWithRedirect = ( { debug , decorators , defaultValue , destroyOnUnregister , form , formRootPathname , initialValues , initialValuesEqual , keepDirtyOnReinitialize = true , mutators = defaultMutators , render , save , saving , subscription = defaultSubscription , validate , validateOnBlur , version , warnWhenUnsavedChanges , sanitizeEmptyValues : shouldSanitizeEmptyValues = true , ... props } ) => { const record = useRecordContext ( props ) ; const redirect = useRef ( props . redirect ) ; const onSave = useRef ( save ) ; const formGroups = useRef < { [ key ] : string [ ] ; } > ( { } ) ; const finalMutators = useMemo ( ( ) => mutators === defaultMutators ? mutators : { ... defaultMutators , ... mutators } , [ mutators ] ) ; const setRedirect = newRedirect => { redirect . current = newRedirect ; } ; /**
     * A form can have several Save buttons. In case the user clicks on
     * a Save button with a custom onSave handler, then on a second Save button
     * without custom onSave handler, the user expects the default save
     * handler (the one of the Form) to be called.
     * That's why the SaveButton onClick calls setOnSave() with no parameters
     * if it has no custom onSave, and why this function forces a default to
     * save.
     */ const setOnSave = useCallback ( newOnSave => { typeof newOnSave === 'function' ? ( onSave . current = newOnSave ) : ( onSave . current = save ) ; } , [ save ] ) ; const formContextValue = useMemo < FormContextValue > ( ( ) => ( { setOnSave , getGroupFields : name => formGroups . current [ name ] || [ ] , registerGroup : name => { formGroups . current [ name ] = formGroups . current [ name ] || [ ] ; } , unregisterGroup : name => { delete formGroups [ name ] ; } , registerField : ( source , group ) => { if ( group ) { const fields = new Set ( formGroups . current [ group ] || [ ] ) ; fields . add ( source ) ; formGroups . current [ group ] = Array . from ( fields ) ; } } , unregisterField : ( source , group ) => { if ( group ) { if ( ! formGroups . current [ group ] ) { console . warn ( `Invalid form group ${ group }` ) ; } else { const fields = new Set ( formGroups . current [ group ] ) ; fields . delete ( source ) ; formGroups . current [ group ] = Array . from ( fields ) ; } } } , } ) , [ setOnSave ] ) ; const finalInitialValues = useMemo ( ( ) => getFormInitialValues ( initialValues , defaultValue , record ) , [ JSON . stringify ( { initialValues , defaultValue , record } ) ] ) ; const submit = values => { const finalRedirect = typeof redirect . current === undefined ? props . redirect : redirect . current ; if ( shouldSanitizeEmptyValues ) { const sanitizedValues = sanitizeEmptyValues ( finalInitialValues , values ) ; return onSave . current ( sanitizedValues , finalRedirect ) ; } else { return onSave . current ( values , finalRedirect ) ; } } ; return ( < RecordContextProvider value = { record } > < FormContextProvider value = { formContextValue } > < Form key = { `${ version }_${ record ?. id || '' }` } debug = { debug } decorators = { decorators } destroyOnUnregister = { destroyOnUnregister } form = { form } initialValues = { finalInitialValues } initialValuesEqual = { initialValuesEqual } keepDirtyOnReinitialize = { keepDirtyOnReinitialize } mutators = { finalMutators } onSubmit = { submit } subscription = { subscription } validate = { validate } validateOnBlur = { validateOnBlur } render = { formProps => ( < FormView { ... props } { ... formProps } key = { `${ version }_${ record ?. id || '' }` } record = { record } setRedirect = { setRedirect } saving = { formProps . submitting || saving } render = { render } save = { save } warnWhenUnsavedChanges = { warnWhenUnsavedChanges } formRootPathname = { formRootPathname } / > ) } /> < /FormContextProvider> < /RecordContextProvider>); } ; export type FormWithRedirectProps = FormWithRedirectOwnProps & Omit < FormProps , 'onSubmit' > ; export type FormWithRedirectRenderProps = Omit < FormViewProps , 'children' | 'render' | 'setRedirect' > ; export type FormWithRedirectRender = ( props : FormWithRedirectRenderProps ) => React . ReactElement < any , any > ; export type FormWithRedirectSave = ( data : Partial < RaRecord > , redirectTo : RedirectionSideEffect , options ? : { onSuccess ? : OnSuccess ; onFailure ? : OnFailure ; } ) => void ; export interface FormWithRedirectOwnProps { defaultValue ? : any ; formRootPathname ? : string ; record ? : Partial < RaRecord > ; redirect ? : RedirectionSideEffect ; render : FormWithRedirectRender ; save ? : FormWithRedirectSave ; sanitizeEmptyValues ? : boolean ; saving ? : boolean ; version ? : number ; warnWhenUnsavedChanges ? : boolean ; } const defaultMutators = { ... arrayMutators , ... submitErrorsMutators , } ; const defaultSubscription = { submitting : true , pristine : true , valid : true , invalid : true , validating : true , } ; export type SetRedirect = ( redirect : RedirectionSideEffect ) => void ; export type HandleSubmitWithRedirect = ( redirect ? : RedirectionSideEffect ) => void ; interface FormViewProps extends FormWithRedirectOwnProps , Omit < FormRenderProps , 'render' | 'component' > { handleSubmitWithRedirect ? : HandleSubmitWithRedirect ; setRedirect : SetRedirect ; warnWhenUnsavedChanges ? : boolean ; } const FormView = ( { formRootPathname , render , warnWhenUnsavedChanges , setRedirect , ... props } ) => { useResetSubmitErrors ( ) ; useWarnWhenUnsavedChanges ( warnWhenUnsavedChanges , formRootPathname ) ; const dispatch = useDispatch ( ) ; const { redirect , handleSubmit , pristine } = props ; useEffect ( ( ) => { dispatch ( setAutomaticRefresh ( pristine ) ) ; } , [ dispatch , pristine ] ) ; example const handleSubmitWithRedirect = useCallback ( ( redirectTo = redirect ) => { setRedirect ( redirectTo ) ; handleSubmit ( ) ; } , [ setRedirect , redirect , handleSubmit ] ) ; return render ( { ... props , handleSubmitWithRedirect , } ) ; } ; export default FormWithRedirect ;