Skip to content

Commit 680c534

Browse files
authored
feat(useWatch): notify watch in preserve mode (#577)
1 parent f2d8ade commit 680c534

File tree

6 files changed

+89
-18
lines changed

6 files changed

+89
-18
lines changed

docs/examples/useWatch.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type FieldType = {
1313
demo2?: string;
1414
id?: number;
1515
demo1?: { demo2?: { demo3?: { demo4?: string } } };
16+
hidden?: string;
1617
};
1718

1819
const Demo = React.memo(() => {
@@ -48,12 +49,13 @@ export default () => {
4849
const demo4 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4'], form);
4950
const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form);
5051
const more = Form.useWatch(['age', 'name', 'gender'], form);
51-
console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, demo5, more);
52+
const hidden = Form.useWatch(['hidden'], { form, preserve: true });
53+
console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, demo5, more, hidden);
5254
return (
5355
<>
5456
<Form
5557
form={form}
56-
initialValues={{ id: 1, age: '10', name: 'default' }}
58+
initialValues={{ id: 1, age: '10', name: 'default', hidden: 'here' }}
5759
onFinish={v => console.log('submit values', v)}
5860
>
5961
no render
@@ -115,6 +117,7 @@ export default () => {
115117
<button onClick={() => setVisible(c => !c)}>isShow name</button>
116118
<button onClick={() => setVisible3(c => !c)}>isShow initialValue</button>
117119
<button onClick={() => setVisible2(c => !c)}>isShow demo2</button>
120+
<button onClick={() => form.setFieldsValue({ hidden: `${form.getFieldsValue(true).hidden || ''}1` })}>change hidden field</button>
118121
</>
119122
);
120123
};

src/interface.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,16 @@ export interface Callbacks<Values = any> {
194194
onFinishFailed?: (errorInfo: ValidateErrorEntity<Values>) => void;
195195
}
196196

197-
export type WatchCallBack = (values: Store, namePathList: InternalNamePath[]) => void;
197+
export type WatchCallBack = (
198+
values: Store,
199+
allValues: Store,
200+
namePathList: InternalNamePath[],
201+
) => void;
202+
203+
export interface WatchOptions<Form extends FormInstance = FormInstance> {
204+
form?: Form;
205+
preserve?: boolean;
206+
}
198207

199208
export interface InternalHooks {
200209
dispatch: (action: ReducerAction) => void;

src/useForm.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,10 @@ export class FormStore {
200200
// No need to cost perf when nothing need to watch
201201
if (this.watchList.length) {
202202
const values = this.getFieldsValue();
203+
const allValues = this.getFieldsValue(true);
203204

204205
this.watchList.forEach(callback => {
205-
callback(values, namePath);
206+
callback(values, allValues, namePath);
206207
});
207208
}
208209
};
@@ -548,7 +549,7 @@ export class FormStore {
548549
const namePathList: InternalNamePath[] = [];
549550

550551
fields.forEach((fieldData: FieldData) => {
551-
const { name, errors, ...data } = fieldData;
552+
const { name, ...data } = fieldData;
552553
const namePath = getNamePath(name);
553554
namePathList.push(namePath);
554555

src/useWatch.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ import type { FormInstance } from '.';
22
import { FieldContext } from '.';
33
import warning from 'rc-util/lib/warning';
44
import { HOOK_MARK } from './FieldContext';
5-
import type { InternalFormInstance, InternalNamePath, NamePath, Store } from './interface';
5+
import type {
6+
InternalFormInstance,
7+
InternalNamePath,
8+
NamePath,
9+
Store,
10+
WatchOptions,
11+
} from './interface';
612
import { useState, useContext, useEffect, useRef, useMemo } from 'react';
713
import { getNamePath, getValue } from './utils/valueUtil';
14+
import { isFormInstance } from './utils/typeUtil';
815

916
type ReturnPromise<T> = T extends Promise<infer ValueType> ? ValueType : never;
1017
type GetGeneric<TForm extends FormInstance> = ReturnPromise<ReturnType<TForm['validateFields']>>;
@@ -38,7 +45,7 @@ function useWatch<
3845
TDependencies4 extends keyof GetGeneric<TForm>[TDependencies1][TDependencies2][TDependencies3],
3946
>(
4047
dependencies: [TDependencies1, TDependencies2, TDependencies3, TDependencies4],
41-
form?: TForm,
48+
form?: TForm | WatchOptions<TForm>,
4249
): GetGeneric<TForm>[TDependencies1][TDependencies2][TDependencies3][TDependencies4];
4350

4451
function useWatch<
@@ -48,7 +55,7 @@ function useWatch<
4855
TDependencies3 extends keyof GetGeneric<TForm>[TDependencies1][TDependencies2],
4956
>(
5057
dependencies: [TDependencies1, TDependencies2, TDependencies3],
51-
form?: TForm,
58+
form?: TForm | WatchOptions<TForm>,
5259
): GetGeneric<TForm>[TDependencies1][TDependencies2][TDependencies3];
5360

5461
function useWatch<
@@ -57,22 +64,34 @@ function useWatch<
5764
TDependencies2 extends keyof GetGeneric<TForm>[TDependencies1],
5865
>(
5966
dependencies: [TDependencies1, TDependencies2],
60-
form?: TForm,
67+
form?: TForm | WatchOptions<TForm>,
6168
): GetGeneric<TForm>[TDependencies1][TDependencies2];
6269

6370
function useWatch<TDependencies extends keyof GetGeneric<TForm>, TForm extends FormInstance>(
6471
dependencies: TDependencies | [TDependencies],
65-
form?: TForm,
72+
form?: TForm | WatchOptions<TForm>,
6673
): GetGeneric<TForm>[TDependencies];
6774

68-
function useWatch<TForm extends FormInstance>(dependencies: [], form?: TForm): GetGeneric<TForm>;
75+
function useWatch<TForm extends FormInstance>(
76+
dependencies: [],
77+
form?: TForm | WatchOptions<TForm>,
78+
): GetGeneric<TForm>;
6979

70-
function useWatch<TForm extends FormInstance>(dependencies: NamePath, form?: TForm): any;
80+
function useWatch<TForm extends FormInstance>(
81+
dependencies: NamePath,
82+
form?: TForm | WatchOptions<TForm>,
83+
): any;
7184

72-
function useWatch<ValueType = Store>(dependencies: NamePath, form?: FormInstance): ValueType;
85+
function useWatch<ValueType = Store>(
86+
dependencies: NamePath,
87+
form?: FormInstance | WatchOptions<FormInstance>,
88+
): ValueType;
89+
90+
function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]) {
91+
const [dependencies = [], _form = {}] = args;
92+
const options = isFormInstance(_form) ? { form: _form } : _form;
93+
const form = options.form;
7394

74-
function useWatch(...args: [NamePath, FormInstance]) {
75-
const [dependencies = [], form] = args;
7695
const [value, setValue] = useState<any>();
7796

7897
const valueStr = useMemo(() => stringify(value), [value]);
@@ -107,8 +126,8 @@ function useWatch(...args: [NamePath, FormInstance]) {
107126
const { getFieldsValue, getInternalHooks } = formInstance;
108127
const { registerWatch } = getInternalHooks(HOOK_MARK);
109128

110-
const cancelRegister = registerWatch(store => {
111-
const newValue = getValue(store, namePathRef.current);
129+
const cancelRegister = registerWatch((values, allValues) => {
130+
const newValue = getValue(options.preserve ? allValues : values, namePathRef.current);
112131
const nextValueStr = stringify(newValue);
113132

114133
// Compare stringify in case it's nest object
@@ -119,7 +138,10 @@ function useWatch(...args: [NamePath, FormInstance]) {
119138
});
120139

121140
// TODO: We can improve this perf in future
122-
const initialValue = getValue(getFieldsValue(), namePathRef.current);
141+
const initialValue = getValue(
142+
options.preserve ? getFieldsValue(true) : getFieldsValue(),
143+
namePathRef.current,
144+
);
123145
setValue(initialValue);
124146

125147
return cancelRegister;

src/utils/typeUtil.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import type { FormInstance, InternalFormInstance } from '../interface';
2+
13
export function toArray<T>(value?: T | T[] | null): T[] {
24
if (value === undefined || value === null) {
35
return [];
46
}
57

68
return Array.isArray(value) ? value : [value];
79
}
10+
11+
export function isFormInstance<T>(form: T | FormInstance): form is FormInstance {
12+
return form && !!(form as InternalFormInstance)._init;
13+
}

tests/useWatch.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,34 @@ describe('useWatch', () => {
407407
);
408408
errorSpy.mockRestore();
409409
});
410+
411+
it('useWatch with preserve option', async () => {
412+
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
413+
const Demo: React.FC = () => {
414+
const [form] = Form.useForm();
415+
const nameValuePreserve = Form.useWatch<string>('name', {
416+
form,
417+
preserve: true,
418+
});
419+
const nameValue = Form.useWatch<string>('name', form);
420+
React.useEffect(() => {
421+
console.log(nameValuePreserve, nameValue);
422+
}, [nameValuePreserve, nameValue]);
423+
return (
424+
<div>
425+
<Form form={form} initialValues={{ name: 'bamboo' }} />
426+
<div className="values">{nameValuePreserve}</div>
427+
<button className="test-btn" onClick={() => form.setFieldValue('name', 'light')} />
428+
</div>
429+
);
430+
};
431+
await act(async () => {
432+
const { container } = render(<Demo />);
433+
await timeout();
434+
expect(logSpy).toHaveBeenCalledWith('bamboo', undefined); // initialValue
435+
fireEvent.click(container.querySelector('.test-btn'));
436+
await timeout();
437+
expect(logSpy).toHaveBeenCalledWith('light', undefined); // after setFieldValue
438+
});
439+
});
410440
});

0 commit comments

Comments
 (0)