diff --git a/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx index f3a01393ea4..9258eb7ab5a 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { Form } from 'react-final-form'; import arrayMutators from 'final-form-arrays'; @@ -7,6 +7,7 @@ import ArrayInput from './ArrayInput'; import NumberInput from './NumberInput'; import TextInput from './TextInput'; import SimpleFormIterator from '../form/SimpleFormIterator'; +import { minLength, required } from 'ra-core'; describe('', () => { const onSubmit = jest.fn(); @@ -126,4 +127,56 @@ describe('', () => { ) ).toEqual(['bar', 'baz']); }); + + it('should apply validation to both itself and its inner inputs', async () => { + const { getByText, getAllByLabelText, queryByText } = render( + ( +
+ + + + + + +
+ )} + /> + ); + + fireEvent.click(getByText('ra.action.add')); + expect(queryByText('array_min_length')).not.toBeNull(); + fireEvent.click(getByText('ra.action.add')); + const firstId = getAllByLabelText('resources.bar.fields.id *')[0]; + fireEvent.change(firstId, { + target: { value: 'aaa' }, + }); + fireEvent.change(firstId, { + target: { value: '' }, + }); + fireEvent.blur(firstId); + const firstFoo = getAllByLabelText('resources.bar.fields.foo *')[0]; + fireEvent.change(firstFoo, { + target: { value: 'aaa' }, + }); + fireEvent.change(firstFoo, { + target: { value: '' }, + }); + fireEvent.blur(firstFoo); + expect(queryByText('array_min_length')).toBeNull(); + await waitFor(() => { + expect(queryByText('id_required')).not.toBeNull(); + expect(queryByText('foo_required')).not.toBeNull(); + }); + }); }); diff --git a/packages/ra-ui-materialui/src/input/ArrayInput.tsx b/packages/ra-ui-materialui/src/input/ArrayInput.tsx index 0e63fcdca84..0fa7acf073b 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput.tsx @@ -96,20 +96,21 @@ const ArrayInput: FC = ({ ); } - const { error, submitError, touched } = fieldProps.meta; + const { error, submitError, touched, dirty } = fieldProps.meta; + const arrayInputError = getArrayInputError(error || submitError); return ( = ({ isRequired={isRequired(validate)} /> - {!!(touched && (error || submitError)) || helperText ? ( - - - - ) : null} {cloneElement(Children.only(children), { ...fieldProps, record, @@ -136,6 +128,15 @@ const ArrayInput: FC = ({ margin, disabled, })} + {!!((touched || dirty) && arrayInputError) || helperText ? ( + + + + ) : null} ); }; @@ -163,6 +164,13 @@ ArrayInput.defaultProps = { fullWidth: true, }; +export const getArrayInputError = error => { + if (Array.isArray(error)) { + return undefined; + } + return error; +}; + export interface ArrayInputProps extends InputProps { children: ReactElement; disabled?: boolean;