diff --git a/docs/examples/useWatch.tsx b/docs/examples/useWatch.tsx index 3a3d3a88..1475b789 100644 --- a/docs/examples/useWatch.tsx +++ b/docs/examples/useWatch.tsx @@ -13,6 +13,7 @@ type FieldType = { demo2?: string; id?: number; demo1?: { demo2?: { demo3?: { demo4?: string } } }; + hidden?: string; }; const Demo = React.memo(() => { @@ -48,12 +49,13 @@ export default () => { const demo4 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4'], form); const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form); const more = Form.useWatch(['age', 'name', 'gender'], form); - console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, demo5, more); + const hidden = Form.useWatch(['hidden'], { form, preserve: true }); + console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, demo5, more, hidden); return ( <>
console.log('submit values', v)} > no render @@ -115,6 +117,7 @@ export default () => { + ); }; diff --git a/src/interface.ts b/src/interface.ts index c01f0839..d10d7e33 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -193,7 +193,16 @@ export interface Callbacks { onFinishFailed?: (errorInfo: ValidateErrorEntity) => void; } -export type WatchCallBack = (values: Store, namePathList: InternalNamePath[]) => void; +export type WatchCallBack = ( + values: Store, + allValues: Store, + namePathList: InternalNamePath[], +) => void; + +export interface WatchOptions { + form?: Form; + preserve?: boolean; +} export interface InternalHooks { dispatch: (action: ReducerAction) => void; diff --git a/src/useForm.ts b/src/useForm.ts index 1adf9967..b4d0103f 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -200,9 +200,10 @@ export class FormStore { // No need to cost perf when nothing need to watch if (this.watchList.length) { const values = this.getFieldsValue(); + const allValues = this.getFieldsValue(true); this.watchList.forEach(callback => { - callback(values, namePath); + callback(values, allValues, namePath); }); } }; @@ -548,7 +549,7 @@ export class FormStore { const namePathList: InternalNamePath[] = []; fields.forEach((fieldData: FieldData) => { - const { name, errors, ...data } = fieldData; + const { name, ...data } = fieldData; const namePath = getNamePath(name); namePathList.push(namePath); diff --git a/src/useWatch.ts b/src/useWatch.ts index 13977820..afaa9846 100644 --- a/src/useWatch.ts +++ b/src/useWatch.ts @@ -2,9 +2,16 @@ import type { FormInstance } from '.'; import { FieldContext } from '.'; import warning from 'rc-util/lib/warning'; import { HOOK_MARK } from './FieldContext'; -import type { InternalFormInstance, InternalNamePath, NamePath, Store } from './interface'; +import type { + InternalFormInstance, + InternalNamePath, + NamePath, + Store, + WatchOptions, +} from './interface'; import { useState, useContext, useEffect, useRef, useMemo } from 'react'; import { getNamePath, getValue } from './utils/valueUtil'; +import { isFormInstance } from './utils/typeUtil'; type ReturnPromise = T extends Promise ? ValueType : never; type GetGeneric = ReturnPromise>; @@ -38,7 +45,7 @@ function useWatch< TDependencies4 extends keyof GetGeneric[TDependencies1][TDependencies2][TDependencies3], >( dependencies: [TDependencies1, TDependencies2, TDependencies3, TDependencies4], - form?: TForm, + form?: TForm | WatchOptions, ): GetGeneric[TDependencies1][TDependencies2][TDependencies3][TDependencies4]; function useWatch< @@ -48,7 +55,7 @@ function useWatch< TDependencies3 extends keyof GetGeneric[TDependencies1][TDependencies2], >( dependencies: [TDependencies1, TDependencies2, TDependencies3], - form?: TForm, + form?: TForm | WatchOptions, ): GetGeneric[TDependencies1][TDependencies2][TDependencies3]; function useWatch< @@ -57,22 +64,34 @@ function useWatch< TDependencies2 extends keyof GetGeneric[TDependencies1], >( dependencies: [TDependencies1, TDependencies2], - form?: TForm, + form?: TForm | WatchOptions, ): GetGeneric[TDependencies1][TDependencies2]; function useWatch, TForm extends FormInstance>( dependencies: TDependencies | [TDependencies], - form?: TForm, + form?: TForm | WatchOptions, ): GetGeneric[TDependencies]; -function useWatch(dependencies: [], form?: TForm): GetGeneric; +function useWatch( + dependencies: [], + form?: TForm | WatchOptions, +): GetGeneric; -function useWatch(dependencies: NamePath, form?: TForm): any; +function useWatch( + dependencies: NamePath, + form?: TForm | WatchOptions, +): any; -function useWatch(dependencies: NamePath, form?: FormInstance): ValueType; +function useWatch( + dependencies: NamePath, + form?: FormInstance | WatchOptions, +): ValueType; + +function useWatch(...args: [NamePath, FormInstance | WatchOptions]) { + const [dependencies = [], _form = {}] = args; + const options = isFormInstance(_form) ? { form: _form } : _form; + const form = options.form; -function useWatch(...args: [NamePath, FormInstance]) { - const [dependencies = [], form] = args; const [value, setValue] = useState(); const valueStr = useMemo(() => stringify(value), [value]); @@ -107,8 +126,8 @@ function useWatch(...args: [NamePath, FormInstance]) { const { getFieldsValue, getInternalHooks } = formInstance; const { registerWatch } = getInternalHooks(HOOK_MARK); - const cancelRegister = registerWatch(store => { - const newValue = getValue(store, namePathRef.current); + const cancelRegister = registerWatch((values, allValues) => { + const newValue = getValue(options.preserve ? allValues : values, namePathRef.current); const nextValueStr = stringify(newValue); // Compare stringify in case it's nest object @@ -119,7 +138,10 @@ function useWatch(...args: [NamePath, FormInstance]) { }); // TODO: We can improve this perf in future - const initialValue = getValue(getFieldsValue(), namePathRef.current); + const initialValue = getValue( + options.preserve ? getFieldsValue(true) : getFieldsValue(), + namePathRef.current, + ); setValue(initialValue); return cancelRegister; diff --git a/src/utils/typeUtil.ts b/src/utils/typeUtil.ts index bb319571..49128367 100644 --- a/src/utils/typeUtil.ts +++ b/src/utils/typeUtil.ts @@ -1,3 +1,5 @@ +import type { FormInstance, InternalFormInstance } from '../interface'; + export function toArray(value?: T | T[] | null): T[] { if (value === undefined || value === null) { return []; @@ -5,3 +7,7 @@ export function toArray(value?: T | T[] | null): T[] { return Array.isArray(value) ? value : [value]; } + +export function isFormInstance(form: T | FormInstance): form is FormInstance { + return form && !!(form as InternalFormInstance)._init; +} diff --git a/tests/useWatch.test.tsx b/tests/useWatch.test.tsx index a53f6a00..6e22819b 100644 --- a/tests/useWatch.test.tsx +++ b/tests/useWatch.test.tsx @@ -407,4 +407,34 @@ describe('useWatch', () => { ); errorSpy.mockRestore(); }); + + it('useWatch with preserve option', async () => { + const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + const Demo: React.FC = () => { + const [form] = Form.useForm(); + const nameValuePreserve = Form.useWatch('name', { + form, + preserve: true, + }); + const nameValue = Form.useWatch('name', form); + React.useEffect(() => { + console.log(nameValuePreserve, nameValue); + }, [nameValuePreserve, nameValue]); + return ( +
+ +
{nameValuePreserve}
+
+ ); + }; + await act(async () => { + const { container } = render(); + await timeout(); + expect(logSpy).toHaveBeenCalledWith('bamboo', undefined); // initialValue + fireEvent.click(container.querySelector('.test-btn')); + await timeout(); + expect(logSpy).toHaveBeenCalledWith('light', undefined); // after setFieldValue + }); + }); });