Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[@mantine/form] useForm: fix isDirty for list fields #3025

Merged
merged 2 commits into from Dec 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/mantine-form/src/tests/dirty.test.ts
Expand Up @@ -40,4 +40,23 @@ describe('@mantine/form/dirty', () => {
act(() => hook.result.current.resetDirty());
expect(hook.result.current.isDirty()).toBe(false);
});

it('sets list field as dirty if list item changes', () => {
const hook = renderHook(() => useForm({ initialValues: { a: [{ b: 1 }, { b: 2 }] } }));
act(() => hook.result.current.setFieldValue('a.0', 3));
expect(hook.result.current.isDirty('a.0')).toBe(true);
expect(hook.result.current.isDirty('a')).toBe(true);

act(() => hook.result.current.setFieldValue('a', [{ b: 1 }, { b: 2 }]));
expect(hook.result.current.isDirty('a.0')).toBe(false);
expect(hook.result.current.isDirty('a')).toBe(false);

act(() => hook.result.current.insertListItem('a', [{ b: 3 }]));
expect(hook.result.current.isDirty('a.2')).toBe(true);
expect(hook.result.current.isDirty('a')).toBe(true);

act(() => hook.result.current.removeListItem('a', 2));
expect(hook.result.current.isDirty('a.2')).toBe(false);
expect(hook.result.current.isDirty('a')).toBe(false);
});
});
1 change: 1 addition & 0 deletions src/mantine-form/src/types.ts
Expand Up @@ -70,6 +70,7 @@ export type SetFieldValue<Values> = <Field extends LooseKeys<Values>>(
) => void;

export type ClearFieldError = (path: unknown) => void;
export type ClearFieldDirty = (path: unknown) => void;
export type ClearErrors = () => void;
export type Reset = () => void;
export type Validate = () => FormValidationResult;
Expand Down
57 changes: 42 additions & 15 deletions src/mantine-form/src/use-form.ts
Expand Up @@ -28,6 +28,7 @@ import {
ResetDirty,
IsValid,
_TransformValues,
ClearFieldDirty,
} from './types';

export function useForm<
Expand Down Expand Up @@ -92,14 +93,25 @@ export function useForm<
[]
);

const clearFieldDirty: ClearFieldDirty = useCallback(
(path) =>
setDirty((current) => {
if (typeof path !== 'string') {
return current;
}

const result = clearListState(path, current);
delete result[path];
return result;
}),
[]
);

const setFieldValue: SetFieldValue<Values> = useCallback((path, value) => {
const shouldValidate = shouldValidateOnChange(path, validateInputOnChange);
clearFieldDirty(path);
setTouched((currentTouched) => ({ ...currentTouched, [path]: true }));
_setValues((current) => {
const initialValue = getPath(path, _dirtyValues.current);
const isFieldDirty = !isEqual(initialValue, value);
setDirty((currentDirty) => ({ ...currentDirty, [path]: isFieldDirty }));
setTouched((currentTouched) => ({ ...currentTouched, [path]: true }));

const result = setPath(path, value, current);

if (shouldValidate) {
Expand All @@ -123,21 +135,21 @@ export function useForm<
clearInputErrorOnChange && clearErrors();
}, []);

const reorderListItem: ReorderListItem<Values> = useCallback(
(path, payload) => _setValues((current) => reorderPath(path, payload, current)),
[]
);
const reorderListItem: ReorderListItem<Values> = useCallback((path, payload) => {
clearFieldDirty(path);
_setValues((current) => reorderPath(path, payload, current));
}, []);

const removeListItem: RemoveListItem<Values> = useCallback((path, index) => {
clearFieldDirty(path);
_setValues((current) => removePath(path, index, current));
_setErrors((errs) => clearListState(path, errs));
setDirty((current) => clearListState(`${String(path)}.${index}`, current));
}, []);

const insertListItem: InsertListItem<Values> = useCallback(
(path, item, index) => _setValues((current) => insertPath(path, item, index, current)),
[]
);
const insertListItem: InsertListItem<Values> = useCallback((path, item, index) => {
clearFieldDirty(path);
_setValues((current) => insertPath(path, item, index, current));
}, []);

const validate: Validate = useCallback(() => {
const results = validateValues(rules, values);
Expand Down Expand Up @@ -204,7 +216,22 @@ export function useForm<
reset();
}, []);

const isDirty: GetFieldStatus<Values> = useCallback((path) => getStatus(dirty, path), [dirty]);
const isDirty: GetFieldStatus<Values> = (path) => {
const isOverridden = Object.keys(dirty).length > 0;

if (isOverridden) {
return getStatus(dirty, path);
}

if (path) {
const sliceOfValues = getPath(path, values);
const sliceOfInitialValues = getPath(path, _dirtyValues.current);
return !isEqual(sliceOfValues, sliceOfInitialValues);
}

return !isEqual(values, _dirtyValues.current);
};

const isTouched: GetFieldStatus<Values> = useCallback(
(path) => getStatus(touched, path),
[touched]
Expand Down