diff --git a/cypress/integration/edit.js b/cypress/integration/edit.js
index d23df5aefa9..496e27277e4 100644
--- a/cypress/integration/edit.js
+++ b/cypress/integration/edit.js
@@ -69,6 +69,17 @@ describe('Edit Page', () => {
);
});
+ it('should validate inputs inside ArrayInput', () => {
+ EditPostPage.gotoTab(3);
+
+ cy.get(EditPostPage.elements.addBacklinkButton).click();
+
+ EditPostPage.clickInput('backlinks[0].url');
+ cy.get(EditPostPage.elements.input('backlinks[0].url')).blur();
+
+ cy.contains('Required');
+ });
+
it('should change reference list correctly when changing filter', () => {
const EditPostTagsPage = editPageFactory('/#/posts/13');
EditPostTagsPage.navigate();
diff --git a/cypress/support/EditPage.js b/cypress/support/EditPage.js
index 4a9314028fc..b9c80aa473e 100644
--- a/cypress/support/EditPage.js
+++ b/cypress/support/EditPage.js
@@ -2,6 +2,7 @@ export default url => ({
elements: {
body: 'body',
deleteButton: '.ra-delete-button',
+ addBacklinkButton: '.button-add-backlinks',
input: (name, type = 'input') => {
if (type === 'rich-text-input') {
return `.ra-input-${name} .ql-editor`;
diff --git a/examples/simple/src/posts/PostEdit.js b/examples/simple/src/posts/PostEdit.js
index 371b9155382..de8a6a5e0dc 100644
--- a/examples/simple/src/posts/PostEdit.js
+++ b/examples/simple/src/posts/PostEdit.js
@@ -144,7 +144,7 @@ const PostEdit = ({ permissions, ...props }) => (
-
+
diff --git a/packages/ra-core/src/form/useFormGroup.ts b/packages/ra-core/src/form/useFormGroup.ts
index 3936912f772..f5a6dbe2005 100644
--- a/packages/ra-core/src/form/useFormGroup.ts
+++ b/packages/ra-core/src/form/useFormGroup.ts
@@ -5,11 +5,12 @@ import { useFormContext } from './useFormContext';
import { FieldState } from 'final-form';
type FormGroupState = {
+ dirty: boolean;
errors: object;
- valid: boolean;
invalid: boolean;
pristine: boolean;
- dirty: boolean;
+ touched: boolean;
+ valid: boolean;
};
/**
@@ -58,18 +59,23 @@ export const useFormGroup = (name: string): FormGroupState => {
const form = useForm();
const formContext = useFormContext();
const [state, setState] = useState({
+ dirty: false,
errors: undefined,
- valid: true,
invalid: false,
pristine: true,
- dirty: false,
+ touched: false,
+ valid: true,
});
useEffect(() => {
const unsubscribe = form.subscribe(
() => {
const fields = formContext.getGroupFields(name);
- const fieldStates = fields.map(form.getFieldState);
+ const fieldStates = fields
+ .map(field => {
+ return form.getFieldState(field);
+ })
+ .filter(fieldState => fieldState != undefined); // eslint-disable-line
const newState = getFormGroupState(fieldStates);
setState(oldState => {
@@ -86,6 +92,7 @@ export const useFormGroup = (name: string): FormGroupState => {
dirty: true,
pristine: true,
valid: true,
+ touched: true,
}
);
return unsubscribe;
@@ -102,8 +109,8 @@ export const useFormGroup = (name: string): FormGroupState => {
*/
export const getFormGroupState = (
fieldStates: FieldState[]
-): FormGroupState =>
- fieldStates.reduce(
+): FormGroupState => {
+ return fieldStates.reduce(
(acc, fieldState) => {
let errors = acc.errors || {};
@@ -112,20 +119,23 @@ export const getFormGroupState = (
}
const newState = {
+ dirty: acc.dirty || fieldState.dirty,
errors,
- valid: acc.valid && fieldState.valid,
invalid: acc.invalid || fieldState.invalid,
pristine: acc.pristine && fieldState.pristine,
- dirty: acc.dirty || fieldState.dirty,
+ touched: acc.touched || fieldState.touched,
+ valid: acc.valid && fieldState.valid,
};
return newState;
},
{
+ dirty: false,
errors: undefined,
- valid: true,
invalid: false,
pristine: true,
- dirty: false,
+ valid: true,
+ touched: false,
}
);
+};
diff --git a/packages/ra-ui-materialui/src/form/FormTab.tsx b/packages/ra-ui-materialui/src/form/FormTab.tsx
index cf369b97945..3cfd3271370 100644
--- a/packages/ra-ui-materialui/src/form/FormTab.tsx
+++ b/packages/ra-ui-materialui/src/form/FormTab.tsx
@@ -93,7 +93,7 @@ export const FormTabHeader = ({
className={classnames('form-tab', className, {
[classes.errorTabButton]:
formGroup.invalid &&
- formGroup.dirty &&
+ formGroup.touched &&
location.pathname !== value,
})}
component={Link}
diff --git a/packages/ra-ui-materialui/src/input/TranslatableInputsTab.tsx b/packages/ra-ui-materialui/src/input/TranslatableInputsTab.tsx
index ab23914f491..9a48783ffb1 100644
--- a/packages/ra-ui-materialui/src/input/TranslatableInputsTab.tsx
+++ b/packages/ra-ui-materialui/src/input/TranslatableInputsTab.tsx
@@ -13,7 +13,8 @@ export const TranslatableInputsTab = (
props: TranslatableInputsTabProps & TabProps
) => {
const { groupKey = '', locale, classes: classesOverride, ...rest } = props;
- const { invalid } = useFormGroup(`${groupKey}${locale}`);
+ const { invalid, touched } = useFormGroup(`${groupKey}${locale}`);
+
const classes = useStyles(props);
const translate = useTranslate();
@@ -23,7 +24,7 @@ export const TranslatableInputsTab = (
label={translate(`ra.locales.${locale}`, {
_: capitalize(locale),
})}
- className={invalid ? classes.error : undefined}
+ className={invalid && touched ? classes.error : undefined}
{...rest}
/>
);