diff --git a/packages/ra-core/src/controller/input/useGetArrayInputNewItemDefaults.ts b/packages/ra-core/src/controller/input/useGetArrayInputNewItemDefaults.ts
index adf9fbc3e30..402834f082b 100644
--- a/packages/ra-core/src/controller/input/useGetArrayInputNewItemDefaults.ts
+++ b/packages/ra-core/src/controller/input/useGetArrayInputNewItemDefaults.ts
@@ -1,4 +1,5 @@
import { Children, isValidElement, useRef, type ReactNode } from 'react';
+import set from 'lodash/set.js';
import { FormDataConsumer } from '../../form/FormDataConsumer';
import type { ArrayInputContextValue } from './ArrayInputContext';
import { useEvent } from '../../util';
@@ -31,15 +32,18 @@ export const useGetArrayInputNewItemDefaults = (
// ArrayInput used for an array of objects
// (e.g. authors: [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Doe' }])
- const defaultValue = initialDefaultValue.current;
+ const defaultValue = { ...initialDefaultValue.current };
Children.forEach(inputs, input => {
if (
isValidElement(input) &&
input.type !== FormDataConsumer &&
input.props.source
) {
- defaultValue[input.props.source] =
- input.props.defaultValue ?? null;
+ set(
+ defaultValue,
+ input.props.source,
+ input.props.defaultValue ?? null
+ );
}
});
return defaultValue;
diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx
index 70c46da832a..b934b93826f 100644
--- a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx
+++ b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.spec.tsx
@@ -505,6 +505,197 @@ describe('', () => {
expect(screen.queryAllByLabelText('ra.action.remove').length).toBe(1);
});
+ it('should not reuse removed values for nested sources when adding a new item', async () => {
+ render(
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(
+ screen
+ .queryAllByLabelText('Stage Manager ID')
+ .map(
+ inputElement => (inputElement as HTMLInputElement).value
+ )
+ ).toEqual(['101', '102', '103']);
+ });
+
+ const lastItem = screen
+ .queryAllByLabelText('Venue')[2]
+ .closest('li') as HTMLElement;
+ const removeLastButton = getByLabelText(
+ lastItem,
+ 'ra.action.remove'
+ ).closest('button') as HTMLButtonElement;
+
+ fireEvent.click(removeLastButton);
+ await waitFor(() => {
+ expect(screen.queryAllByLabelText('Venue').length).toEqual(2);
+ });
+
+ fireEvent.click(
+ screen
+ .getByLabelText('ra.action.add')
+ .closest('button') as HTMLButtonElement
+ );
+
+ await waitFor(() => {
+ expect(screen.queryAllByLabelText('Venue').length).toEqual(3);
+ });
+
+ expect(
+ screen
+ .queryAllByLabelText('Stage Manager ID')
+ .map(inputElement => (inputElement as HTMLInputElement).value)
+ ).toEqual(['101', '102', '']);
+ expect(
+ screen
+ .queryAllByLabelText('Ticket Tier')
+ .map(inputElement => (inputElement as HTMLInputElement).value)
+ ).toEqual(['', 'premium', '']);
+ expect(
+ screen
+ .queryAllByLabelText('Language')
+ .map(inputElement => (inputElement as HTMLInputElement).value)
+ ).toEqual(['', 'en', '']);
+ });
+
+ it('should create nested null defaults for nested sources when adding a new item', async () => {
+ const save = jest.fn();
+
+ render(
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const firstItem = screen
+ .queryAllByLabelText('Venue')[0]
+ .closest('li') as HTMLElement;
+ const removeFirstButton = getByLabelText(
+ firstItem,
+ 'ra.action.remove'
+ ).closest('button') as HTMLButtonElement;
+
+ fireEvent.click(removeFirstButton);
+ await waitFor(() => {
+ expect(screen.queryAllByLabelText('Venue').length).toEqual(0);
+ });
+
+ fireEvent.click(
+ screen
+ .getByLabelText('ra.action.add')
+ .closest('button') as HTMLButtonElement
+ );
+
+ await waitFor(() => {
+ expect(screen.queryAllByLabelText('Venue').length).toEqual(1);
+ });
+
+ fireEvent.click(screen.getByText('ra.action.save'));
+
+ await waitFor(() => {
+ expect(save).toHaveBeenCalled();
+ });
+
+ expect(save.mock.calls[0][0]).toEqual({
+ id: 1,
+ venueList: [
+ {
+ venue: null,
+ details: {
+ stageManagerId: null,
+ ticketTier: null,
+ language: null,
+ },
+ },
+ ],
+ });
+ });
+
it('should remove children row on remove button click', async () => {
const emails = [{ email: 'foo@bar.com' }, { email: 'bar@foo.com' }];