Skip to content

Commit c9d8d68

Browse files
committed
Fix <Form> ignores resetOptions when record changes
1 parent 8d3f662 commit c9d8d68

File tree

2 files changed

+136
-2
lines changed

2 files changed

+136
-2
lines changed

packages/ra-core/src/form/Form.spec.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,129 @@ describe('Form', () => {
120120
expect(screen.getByText('isDirty: false')).not.toBeNull();
121121
});
122122

123+
it('should keep dirty values when record changes and resetOptions.keepDirtyValues is true', async () => {
124+
const { rerender } = render(
125+
<CoreAdminContext>
126+
<Form
127+
onSubmit={jest.fn()}
128+
record={{ id: 1, name: 'Initial' }}
129+
resetOptions={{ keepDirtyValues: true }}
130+
>
131+
<Input source="name" />
132+
</Form>
133+
</CoreAdminContext>
134+
);
135+
136+
expect(screen.getByDisplayValue('Initial')).not.toBeNull();
137+
138+
fireEvent.change(screen.getByLabelText('name'), {
139+
target: { value: 'User Modified' },
140+
});
141+
142+
await waitFor(() => {
143+
expect(screen.getByDisplayValue('User Modified')).not.toBeNull();
144+
});
145+
146+
rerender(
147+
<CoreAdminContext>
148+
<Form
149+
onSubmit={jest.fn()}
150+
record={{
151+
id: 1,
152+
name: 'Server Updated',
153+
otherField: 'new value',
154+
}}
155+
resetOptions={{ keepDirtyValues: true }}
156+
>
157+
<Input source="name" />
158+
</Form>
159+
</CoreAdminContext>
160+
);
161+
162+
await waitFor(() => {
163+
expect(screen.getByDisplayValue('User Modified')).not.toBeNull();
164+
});
165+
expect(screen.queryByDisplayValue('Server Updated')).toBeNull();
166+
});
167+
168+
it('should NOT keep dirty values when record changes and resetOptions.keepDirtyValues is false', async () => {
169+
const { rerender } = render(
170+
<CoreAdminContext>
171+
<Form onSubmit={jest.fn()} record={{ id: 1, name: 'Initial' }}>
172+
<Input source="name" />
173+
</Form>
174+
</CoreAdminContext>
175+
);
176+
177+
fireEvent.change(screen.getByLabelText('name'), {
178+
target: { value: 'User Modified' },
179+
});
180+
181+
await waitFor(() => {
182+
expect(screen.getByDisplayValue('User Modified')).not.toBeNull();
183+
});
184+
185+
rerender(
186+
<CoreAdminContext>
187+
<Form
188+
onSubmit={jest.fn()}
189+
record={{
190+
id: 1,
191+
name: 'Server Updated',
192+
}}
193+
>
194+
<Input source="name" />
195+
</Form>
196+
</CoreAdminContext>
197+
);
198+
199+
await waitFor(() => {
200+
expect(screen.getByDisplayValue('Server Updated')).not.toBeNull();
201+
});
202+
expect(screen.queryByDisplayValue('User Modified')).toBeNull();
203+
});
204+
205+
it('should NOT keep dirty values when switching to a different record even with resetOptions.keepDirtyValues', async () => {
206+
const { rerender } = render(
207+
<CoreAdminContext>
208+
<Form
209+
onSubmit={jest.fn()}
210+
record={{ id: 1, name: 'Record 1' }}
211+
resetOptions={{ keepDirtyValues: true }}
212+
>
213+
<Input source="name" />
214+
</Form>
215+
</CoreAdminContext>
216+
);
217+
218+
fireEvent.change(screen.getByLabelText('name'), {
219+
target: { value: 'User Modified Record 1' },
220+
});
221+
222+
await waitFor(() => {
223+
expect(
224+
screen.getByDisplayValue('User Modified Record 1')
225+
).not.toBeNull();
226+
});
227+
228+
rerender(
229+
<CoreAdminContext>
230+
<Form
231+
onSubmit={jest.fn()}
232+
record={{ id: 2, name: 'Record 2' }}
233+
resetOptions={{ keepDirtyValues: true }}
234+
>
235+
<Input source="name" />
236+
</Form>
237+
</CoreAdminContext>
238+
);
239+
240+
await waitFor(() => {
241+
expect(screen.getByDisplayValue('Record 2')).not.toBeNull();
242+
});
243+
expect(screen.queryByDisplayValue('User Modified Record 1')).toBeNull();
244+
});
245+
123246
it('should update Form state on submit', async () => {
124247
let isSubmitting;
125248

packages/ra-core/src/form/useAugmentedForm.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from 'react';
88
import {
99
FieldValues,
10+
KeepStateOptions,
1011
SubmitHandler,
1112
useForm,
1213
UseFormProps,
@@ -43,6 +44,7 @@ export const useAugmentedForm = <RecordType = any>(
4344
defaultValues,
4445
formRootPathname,
4546
resolver,
47+
resetOptions,
4648
reValidateMode = 'onChange',
4749
onSubmit,
4850
sanitizeEmptyValues,
@@ -86,9 +88,17 @@ export const useAugmentedForm = <RecordType = any>(
8688
const { reset, formState } = form;
8789
const { isReady } = formState;
8890

91+
const previousRecordId = useRef(record?.id);
92+
8993
useEffect(() => {
90-
reset(defaultValuesIncludingRecord);
91-
}, [defaultValuesIncludingRecord, reset]);
94+
const recordIdChanged = record?.id !== previousRecordId.current;
95+
previousRecordId.current = record?.id;
96+
97+
reset(
98+
defaultValuesIncludingRecord,
99+
recordIdChanged ? undefined : resetOptions
100+
);
101+
}, [defaultValuesIncludingRecord, reset, resetOptions, record?.id]);
92102

93103
// notify on invalid form
94104
useNotifyIsFormInvalid(form.control, !disableInvalidFormNotification);
@@ -154,6 +164,7 @@ export interface UseFormOwnProps<RecordType = any> {
154164
defaultValues?: any;
155165
formRootPathname?: string;
156166
record?: Partial<RaRecord>;
167+
resetOptions?: KeepStateOptions;
157168
onSubmit?: SubmitHandler<FieldValues> | SaveHandler<RecordType>;
158169
sanitizeEmptyValues?: boolean;
159170
disableInvalidFormNotification?: boolean;

0 commit comments

Comments
 (0)