From e046b0b6e13e918f4ba33245aac4646a7fe77cde Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Fri, 26 Feb 2021 14:29:18 +0100 Subject: [PATCH 1/3] Reset submission error when the field is changed --- packages/ra-core/package.json | 1 + .../src/form/FormWithRedirect.spec.tsx | 4 +-- .../ra-core/src/form/FormWithRedirect.tsx | 8 +++++- packages/ra-core/src/form/index.ts | 2 ++ .../ra-core/src/form/useResetSubmitErrors.ts | 25 +++++++++++++++++++ packages/react-admin/package.json | 1 + yarn.lock | 5 ++++ 7 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 packages/ra-core/src/form/useResetSubmitErrors.ts diff --git a/packages/ra-core/package.json b/packages/ra-core/package.json index 49f3cadaf68..8289d33255d 100644 --- a/packages/ra-core/package.json +++ b/packages/ra-core/package.json @@ -55,6 +55,7 @@ "peerDependencies": { "connected-react-router": "^6.5.2", "final-form": "^4.18.5", + "final-form-submit-errors": "^0.1.2", "react": "^16.9.0 || ^17.0.0", "react-dom": "^16.9.0 || ^17.0.0", "react-final-form": "^6.3.3", diff --git a/packages/ra-core/src/form/FormWithRedirect.spec.tsx b/packages/ra-core/src/form/FormWithRedirect.spec.tsx index 8ace2e19536..b0ffe9f79cb 100644 --- a/packages/ra-core/src/form/FormWithRedirect.spec.tsx +++ b/packages/ra-core/src/form/FormWithRedirect.spec.tsx @@ -41,7 +41,7 @@ describe('FormWithRedirect', () => { expect(renderProp.mock.calls[1][0].pristine).toEqual(true); expect(getByDisplayValue('Foo')).not.toBeNull(); - expect(renderProp).toHaveBeenCalledTimes(2); + expect(renderProp).toHaveBeenCalledTimes(3); }); it('Does not make the form dirty when reinitialized from a different record', () => { @@ -75,6 +75,6 @@ describe('FormWithRedirect', () => { expect(renderProp.mock.calls[1][0].pristine).toEqual(true); expect(getByDisplayValue('Foo')).not.toBeNull(); - expect(renderProp).toHaveBeenCalledTimes(2); + expect(renderProp).toHaveBeenCalledTimes(3); }); }); diff --git a/packages/ra-core/src/form/FormWithRedirect.tsx b/packages/ra-core/src/form/FormWithRedirect.tsx index db1850c8043..325d091fdb5 100644 --- a/packages/ra-core/src/form/FormWithRedirect.tsx +++ b/packages/ra-core/src/form/FormWithRedirect.tsx @@ -6,9 +6,11 @@ import { FormRenderProps as FinalFormFormRenderProps, } from 'react-final-form'; import arrayMutators from 'final-form-arrays'; +import { submitErrorsMutators } from 'final-form-submit-errors'; import useInitializeFormWithRecord from './useInitializeFormWithRecord'; import useWarnWhenUnsavedChanges from './useWarnWhenUnsavedChanges'; +import useResetSubmitErrors from './useResetSubmitErrors'; import sanitizeEmptyValues from './sanitizeEmptyValues'; import getFormInitialValues from './getFormInitialValues'; import { FormContextValue, Record, OnSuccess, OnFailure } from '../types'; @@ -51,7 +53,10 @@ const FormWithRedirect: FC = ({ initialValues, initialValuesEqual, keepDirtyOnReinitialize = true, - mutators = arrayMutators as any, // FIXME see https://github.com/final-form/react-final-form/issues/704 and https://github.com/microsoft/TypeScript/issues/35771 + mutators = { + ...arrayMutators, + ...submitErrorsMutators, + }, record, render, save, @@ -236,6 +241,7 @@ const FormView: FC = ({ // if record changes (after a getOne success or a refresh), the form must be updated useInitializeFormWithRecord(props.record); useWarnWhenUnsavedChanges(warnWhenUnsavedChanges); + useResetSubmitErrors(); const dispatch = useDispatch(); useEffect(() => { diff --git a/packages/ra-core/src/form/index.ts b/packages/ra-core/src/form/index.ts index 540c7b00a93..eb2e75fbfe9 100644 --- a/packages/ra-core/src/form/index.ts +++ b/packages/ra-core/src/form/index.ts @@ -23,6 +23,7 @@ import useChoices, { } from './useChoices'; import useSuggestions from './useSuggestions'; import useWarnWhenUnsavedChanges from './useWarnWhenUnsavedChanges'; +import useResetSubmitErrors from './useResetSubmitErrors'; export type { ChoicesProps, @@ -52,6 +53,7 @@ export { useSuggestions, ValidationError, useWarnWhenUnsavedChanges, + useResetSubmitErrors, }; export { isRequired } from './FormField'; export * from './validate'; diff --git a/packages/ra-core/src/form/useResetSubmitErrors.ts b/packages/ra-core/src/form/useResetSubmitErrors.ts new file mode 100644 index 00000000000..ac0836ec290 --- /dev/null +++ b/packages/ra-core/src/form/useResetSubmitErrors.ts @@ -0,0 +1,25 @@ +import { useRef } from 'react'; +import { useForm, useFormState } from 'react-final-form'; + +/** + * Reset the submission error when the corresponding field changes. + * final-form doesn't do this by default. + */ +const useResetSubmitErrors = () => { + const { mutators } = useForm(); + const { values } = useFormState({ subscription: { values: true } }); + const prevValues = useRef(values); + useFormState({ + onChange: ({ values }) => { + mutators.resetSubmitErrors({ + current: values, + prev: prevValues.current, + }); + + prevValues.current = values; + }, + subscription: { values: true }, + }); +}; + +export default useResetSubmitErrors; diff --git a/packages/react-admin/package.json b/packages/react-admin/package.json index 25372adc09c..3eb2f8fff1f 100644 --- a/packages/react-admin/package.json +++ b/packages/react-admin/package.json @@ -40,6 +40,7 @@ "connected-react-router": "^6.5.2", "final-form": "^4.20.0", "final-form-arrays": "^3.0.1", + "final-form-submit-errors": "^0.1.2", "ra-core": "~3.13.1", "ra-i18n-polyglot": "~3.13.1", "ra-language-english": "~3.13.1", diff --git a/yarn.lock b/yarn.lock index ee51fff02d2..36c3892ac80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8217,6 +8217,11 @@ final-form-arrays@^3.0.1: resolved "https://registry.yarnpkg.com/final-form-arrays/-/final-form-arrays-3.0.2.tgz#9f3bef778dec61432357744eb6f3abef7e7f3847" integrity sha512-TfO8aZNz3RrsZCDx8GHMQcyztDNpGxSSi9w4wpSNKlmv2PfFWVVM8P7Yj5tj4n0OWax+x5YwTLhT5BnqSlCi+w== +final-form-submit-errors@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/final-form-submit-errors/-/final-form-submit-errors-0.1.2.tgz#1c59d10386d7b5a1a5e89f9389fa7cf85d1255c5" + integrity sha512-2EwHSdf9Sy80bnsGRrDKBLp5C2uY7hL65o8tpqK/FWxyBpXtT519SsIZC4etLpGen2kjFCpYrxYkzFUfbIEXsQ== + final-form@^4.20.0: version "4.20.0" resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.20.0.tgz#454ba46f783a4c4404ad875cf36f470395ad5efa" From f11ba2da619f8ae95113886721e322be65900ce5 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Sat, 27 Feb 2021 12:15:54 +0100 Subject: [PATCH 2/3] Do not use useFormState --- .../src/form/FormWithRedirect.spec.tsx | 4 +-- .../ra-core/src/form/useResetSubmitErrors.ts | 32 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/ra-core/src/form/FormWithRedirect.spec.tsx b/packages/ra-core/src/form/FormWithRedirect.spec.tsx index b0ffe9f79cb..8ace2e19536 100644 --- a/packages/ra-core/src/form/FormWithRedirect.spec.tsx +++ b/packages/ra-core/src/form/FormWithRedirect.spec.tsx @@ -41,7 +41,7 @@ describe('FormWithRedirect', () => { expect(renderProp.mock.calls[1][0].pristine).toEqual(true); expect(getByDisplayValue('Foo')).not.toBeNull(); - expect(renderProp).toHaveBeenCalledTimes(3); + expect(renderProp).toHaveBeenCalledTimes(2); }); it('Does not make the form dirty when reinitialized from a different record', () => { @@ -75,6 +75,6 @@ describe('FormWithRedirect', () => { expect(renderProp.mock.calls[1][0].pristine).toEqual(true); expect(getByDisplayValue('Foo')).not.toBeNull(); - expect(renderProp).toHaveBeenCalledTimes(3); + expect(renderProp).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/ra-core/src/form/useResetSubmitErrors.ts b/packages/ra-core/src/form/useResetSubmitErrors.ts index ac0836ec290..2607d50ed07 100644 --- a/packages/ra-core/src/form/useResetSubmitErrors.ts +++ b/packages/ra-core/src/form/useResetSubmitErrors.ts @@ -1,25 +1,27 @@ -import { useRef } from 'react'; -import { useForm, useFormState } from 'react-final-form'; +import { useEffect, useRef } from 'react'; +import { useForm } from 'react-final-form'; /** * Reset the submission error when the corresponding field changes. * final-form doesn't do this by default. */ const useResetSubmitErrors = () => { - const { mutators } = useForm(); - const { values } = useFormState({ subscription: { values: true } }); - const prevValues = useRef(values); - useFormState({ - onChange: ({ values }) => { - mutators.resetSubmitErrors({ - current: values, - prev: prevValues.current, - }); + const form = useForm(); + const prevValues = useRef(form.getState().values); + useEffect(() => { + const unsubscribe = form.subscribe( + ({ values }) => { + form.mutators.resetSubmitErrors({ + current: values, + prev: prevValues.current, + }); - prevValues.current = values; - }, - subscription: { values: true }, - }); + prevValues.current = values; + }, + { values: true } + ); + return unsubscribe; + }, [form]); }; export default useResetSubmitErrors; From 56e673393fbcc57a0bb91677294dae530fb30507 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Sat, 27 Feb 2021 12:18:42 +0100 Subject: [PATCH 3/3] Create defaultMutators outside of FormWithRedirect --- packages/ra-core/src/form/FormWithRedirect.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/ra-core/src/form/FormWithRedirect.tsx b/packages/ra-core/src/form/FormWithRedirect.tsx index 325d091fdb5..dbe40dac831 100644 --- a/packages/ra-core/src/form/FormWithRedirect.tsx +++ b/packages/ra-core/src/form/FormWithRedirect.tsx @@ -53,10 +53,7 @@ const FormWithRedirect: FC = ({ initialValues, initialValuesEqual, keepDirtyOnReinitialize = true, - mutators = { - ...arrayMutators, - ...submitErrorsMutators, - }, + mutators = defaultMutators, record, render, save, @@ -213,6 +210,11 @@ export interface FormWithRedirectOwnProps { warnWhenUnsavedChanges?: boolean; } +const defaultMutators = { + ...arrayMutators, + ...submitErrorsMutators, +}; + const defaultSubscription = { submitting: true, pristine: true,