Skip to content

Commit 4eebe87

Browse files
committed
Extract dirty computation logic in a hook
1 parent 2510900 commit 4eebe87

File tree

3 files changed

+53
-44
lines changed

3 files changed

+53
-44
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './useWarnWhenUnsavedChanges';
1111
export * from './validation';
1212
export * from './WarnWhenUnsavedChanges';
1313
export * from './FilterLiveForm';
14+
export * from './useFormIsDirty';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { useFormState } from 'react-hook-form';
2+
import isEmpty from 'lodash/isEmpty.js';
3+
4+
// useFormState().isDirty might differ from useFormState().dirtyFields (https://github.com/react-hook-form/react-hook-form/issues/4740)
5+
export const useFormIsDirty = (): boolean => {
6+
const { dirtyFields } = useFormState();
7+
return hasDirtyFields(dirtyFields);
8+
};
9+
10+
const hasDirtyFields = (
11+
dirtyFields: Partial<
12+
Readonly<{
13+
[x: string]: any;
14+
}>
15+
>
16+
): boolean => {
17+
// dirtyFields can contains simple keys with boolean values, nested objects or arrays
18+
// We must ignore values that are false
19+
return Object.values(dirtyFields).some(value => {
20+
if (typeof value === 'boolean') {
21+
return value;
22+
} else if (Array.isArray(value)) {
23+
// Some arrays contain only booleans (scalar arrays), some arrays contain objects (object arrays)
24+
for (const item of value) {
25+
if (item === true) {
26+
return true;
27+
}
28+
// FIXME: because we currently don't set default values correctly for arrays,
29+
// new items are either empty objects, or undefined in dirtyFields. Consider them as dirty.
30+
if (
31+
(typeof item === 'object' && isEmpty(item)) ||
32+
item === undefined
33+
) {
34+
return true;
35+
}
36+
if (
37+
typeof item === 'object' &&
38+
item !== null &&
39+
hasDirtyFields(item)
40+
) {
41+
return true;
42+
}
43+
}
44+
} else if (typeof value === 'object' && value !== null) {
45+
return hasDirtyFields(value);
46+
}
47+
return false;
48+
});
49+
};

packages/ra-ui-materialui/src/button/SaveButton.tsx

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import {
1919
warning,
2020
setSubmissionErrors,
2121
useRecordFromLocation,
22+
useFormIsDirty,
2223
} from 'ra-core';
23-
import isEmpty from 'lodash/isEmpty';
2424

2525
/**
2626
* Submit button for resource forms (Edit and Create).
@@ -73,9 +73,9 @@ export const SaveButton = <RecordType extends RaRecord = any>(
7373
const translate = useTranslate();
7474
const form = useFormContext();
7575
const saveContext = useSaveContext();
76-
const { dirtyFields, isValidating, isSubmitting } = useFormState();
76+
const { isValidating, isSubmitting } = useFormState();
7777
// useFormState().isDirty might differ from useFormState().dirtyFields (https://github.com/react-hook-form/react-hook-form/issues/4740)
78-
const isDirty = hasDirtyFields(dirtyFields);
78+
const isDirty = useFormIsDirty();
7979
// Use form isDirty, isValidating and form context saving to enable or disable the save button
8080
// if alwaysEnable is undefined and the form wasn't prefilled
8181
const recordFromLocation = useRecordFromLocation();
@@ -227,44 +227,3 @@ declare module '@mui/material/styles' {
227227
};
228228
}
229229
}
230-
231-
const hasDirtyFields = (
232-
dirtyFields: Partial<
233-
Readonly<{
234-
[x: string]: any;
235-
}>
236-
>
237-
): boolean => {
238-
// dirtyFields can contains simple keys with boolean values, nested objects or arrays
239-
// We must ignore values that are false
240-
return Object.values(dirtyFields).some(value => {
241-
if (typeof value === 'boolean') {
242-
return value;
243-
} else if (Array.isArray(value)) {
244-
// Some arrays contain only booleans (scalar arrays), some arrays contain objects (object arrays)
245-
for (const item of value) {
246-
if (item === true) {
247-
return true;
248-
}
249-
// FIXME: because we currently don't set default values correctly for arrays,
250-
// new items are either empty objects, or undefined in dirtyFields. Consider them as dirty.
251-
if (
252-
(typeof item === 'object' && isEmpty(item)) ||
253-
item === undefined
254-
) {
255-
return true;
256-
}
257-
if (
258-
typeof item === 'object' &&
259-
item !== null &&
260-
hasDirtyFields(item)
261-
) {
262-
return true;
263-
}
264-
}
265-
} else if (typeof value === 'object' && value !== null) {
266-
return hasDirtyFields(value);
267-
}
268-
return false;
269-
});
270-
};

0 commit comments

Comments
 (0)