diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx index 5d5c107f7ca..4676dee5664 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.spec.tsx @@ -15,7 +15,11 @@ import { TextInput } from '../TextInput'; import { ArrayInput } from './ArrayInput'; import { SimpleFormIterator } from './SimpleFormIterator'; import { useFormContext } from 'react-hook-form'; -import { GlobalValidation, ValidationInFormTab } from './ArrayInput.stories'; +import { + GlobalValidation, + ScalarWithValidation, + ValidationInFormTab, +} from './ArrayInput.stories'; describe('', () => { it('should pass its record props to its child', async () => { @@ -359,6 +363,19 @@ describe('', () => { }); }); + it('should correctly update validation state after removing an item', async () => { + render(); + + await screen.findByDisplayValue('classic'); + fireEvent.click(await screen.findByLabelText('Add')); + fireEvent.click(await screen.findByText('Save')); + await screen.findByText('Required'); + fireEvent.click((await screen.findAllByLabelText('Remove'))[0]); + await waitFor(() => { + expect(screen.queryByText('Required')).toBeNull(); + }); + }); + describe('used within a form with global validation', () => { it('should display an error if the array is required and empty', async () => { render(); @@ -366,10 +383,15 @@ describe('', () => { const RemoveButtons = screen.getAllByLabelText('Remove'); fireEvent.click(RemoveButtons[1]); fireEvent.click(RemoveButtons[0]); + await waitFor(() => { + expect(screen.queryAllByLabelText('Remove')).toHaveLength(0); + }); const SaveButton = screen.getByText('Save'); fireEvent.click(SaveButton); await screen.findByText( - 'The form is not valid. Please check for errors' + 'The form is not valid. Please check for errors', + undefined, + { timeout: 3000 } ); }); it('should display an error if one of the required field is empty', async () => { diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx index b8b8f75d8ee..3eb82030a05 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx @@ -199,6 +199,38 @@ export const Scalar = () => ( ); +export const ScalarWithValidation = () => ( + + ( + { + console.log(data); + }, + }} + > + + + + + + + + + + )} + /> + +); + const order = { id: 1, date: '2022-08-30', diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx index 3fc87585479..4b3e5263830 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.tsx @@ -61,7 +61,7 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => { } = props; const [confirmIsOpen, setConfirmIsOpen] = useState(false); const { append, fields, move, remove, replace } = useArrayInput(props); - const { resetField } = useFormContext(); + const { resetField, trigger, getValues } = useFormContext(); const translate = useTranslate(); const record = useRecordContext(props); const initialDefaultValue = useRef({}); @@ -69,8 +69,16 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => { const removeField = useCallback( (index: number) => { remove(index); + const isScalarArray = getValues(source).every( + (value: any) => typeof value !== 'object' + ); + if (isScalarArray) { + // Trigger validation on the Array to avoid ghost errors. + // Otherwise, validation errors on removed fields might still be displayed + trigger(source); + } }, - [remove] + [remove, trigger, source, getValues] ); if (fields.length > 0) {