Skip to content

Commit

Permalink
[@mantine/form] Fix form.watch callbacks not being fired when `form…
Browse files Browse the repository at this point in the history
….initialize` is called (#6639)

* [@mantine/form] Call `form.watch` subscribers on `form.initialize`

Fixes #6638

* Refactor duplicated logic in form initialize
  • Loading branch information
raulfpl authored Aug 7, 2024
1 parent 4a17e88 commit 8e252e6
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 19 deletions.
37 changes: 35 additions & 2 deletions packages/@mantine/form/src/tests/use-form/watch.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react';
import { act, render, renderHook, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { FormFieldSubscriber } from '../../types';
import { useForm } from '../../use-form';
Expand Down Expand Up @@ -40,7 +40,7 @@ describe('@mantine/form/watch', () => {
});
});

it('does now call subscriber function when other field changes', async () => {
it('does not call subscriber function when other field changes', async () => {
const spy = jest.fn();
render(<TestComponent watch={spy} />);
expect(spy).not.toHaveBeenCalled();
Expand All @@ -49,4 +49,37 @@ describe('@mantine/form/watch', () => {

expect(spy).not.toHaveBeenCalled();
});

it('calls subscriber function when field changes due to form initialize', async () => {
const hook = renderHook(() =>
useForm({ mode: 'uncontrolled', initialValues: { a: '', b: '' } })
);
const spy = jest.fn();

act(() => renderHook(() => hook.result.current.watch('a', spy)));
expect(spy).not.toHaveBeenCalled();

act(() => hook.result.current.initialize({ a: 'c', b: '' }));

expect(spy).toHaveBeenCalledWith({
previousValue: '',
value: 'c',
touched: false,
dirty: false,
});
});

it('does not call subscriber function when other field changes due to form initialize', async () => {
const hook = renderHook(() =>
useForm({ mode: 'uncontrolled', initialValues: { a: '', b: '' } })
);
const spy = jest.fn();

act(() => renderHook(() => hook.result.current.watch('a', spy)));
expect(spy).not.toHaveBeenCalled();

act(() => hook.result.current.initialize({ a: '', b: 'd' }));

expect(spy).not.toHaveBeenCalled();
});
});
46 changes: 29 additions & 17 deletions packages/@mantine/form/src/use-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,33 @@ export function useForm<
mode === 'uncontrolled' && setFormKey((key) => key + 1);
}, []);

const initialize: Initialize<Values> = useCallback((values) => {
$values.initialize(values, () => mode === 'uncontrolled' && setFormKey((key) => key + 1));
}, []);
const handleValuesChanges = useCallback(
(previousValues: Values) => {
clearInputErrorOnChange && $errors.clearErrors();
mode === 'uncontrolled' && setFormKey((key) => key + 1);

Object.keys($watch.subscribers.current).forEach((path) => {
const value = getPath(path, $values.refValues.current);
const previousValue = getPath(path, previousValues);

if (value !== previousValue) {
$watch
.getFieldSubscribers(path)
.forEach((cb) => cb({ previousValues, updatedValues: $values.refValues.current }));
}
});
},
[clearInputErrorOnChange]
);

const initialize: Initialize<Values> = useCallback(
(values) => {
const previousValues = $values.refValues.current;
$values.initialize(values, () => mode === 'uncontrolled' && setFormKey((key) => key + 1));
handleValuesChanges(previousValues);
},
[handleValuesChanges]
);

const setFieldValue: SetFieldValue<Values> = useCallback(
(path, value, options) => {
Expand Down Expand Up @@ -106,21 +130,9 @@ export function useForm<
(values) => {
const previousValues = $values.refValues.current;
$values.setValues({ values, updateState: mode === 'controlled' });
clearInputErrorOnChange && $errors.clearErrors();
mode === 'uncontrolled' && setFormKey((key) => key + 1);

Object.keys($watch.subscribers.current).forEach((path) => {
const value = getPath(path, $values.refValues.current);
const previousValue = getPath(path, previousValues);

if (value !== previousValue) {
$watch
.getFieldSubscribers(path)
.forEach((cb) => cb({ previousValues, updatedValues: $values.refValues.current }));
}
});
handleValuesChanges(previousValues);
},
[onValuesChange, clearInputErrorOnChange]
[onValuesChange, handleValuesChanges]
);

const validate: Validate = useCallback(() => {
Expand Down

0 comments on commit 8e252e6

Please sign in to comment.