Skip to content

Commit 7268791

Browse files
authored
fix: useWatch object trigger re-render (#433)
* test: test driven * test: test driven * fix: object path
1 parent a1dd96d commit 7268791

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

src/useWatch.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@ import { FieldContext } from '.';
33
import warning from 'rc-util/lib/warning';
44
import { HOOK_MARK } from './FieldContext';
55
import type { InternalFormInstance, NamePath, Store } from './interface';
6-
import { useState, useContext, useEffect, useRef } from 'react';
6+
import { useState, useContext, useEffect, useRef, useMemo } from 'react';
77
import { getNamePath, getValue } from './utils/valueUtil';
88

99
type ReturnPromise<T> = T extends Promise<infer ValueType> ? ValueType : never;
1010
type GetGeneric<TForm extends FormInstance> = ReturnPromise<ReturnType<TForm['validateFields']>>;
1111

12+
function stringify(value: any) {
13+
try {
14+
return JSON.stringify(value);
15+
} catch (err) {
16+
return Math.random();
17+
}
18+
}
19+
1220
function useWatch<
1321
TDependencies1 extends keyof GetGeneric<TForm>,
1422
TForm extends FormInstance,
@@ -52,8 +60,10 @@ function useWatch<ValueType = Store>(dependencies: NamePath, form?: FormInstance
5260

5361
function useWatch(dependencies: NamePath = [], form?: FormInstance) {
5462
const [value, setValue] = useState<any>();
55-
const valueCacheRef = useRef<any>();
56-
valueCacheRef.current = value;
63+
64+
const valueStr = useMemo(() => stringify(value), [value]);
65+
const valueStrRef = useRef(valueStr);
66+
valueStrRef.current = valueStr;
5767

5868
const fieldContext = useContext(FieldContext);
5969
const formInstance = (form as InternalFormInstance) || fieldContext;
@@ -83,7 +93,10 @@ function useWatch(dependencies: NamePath = [], form?: FormInstance) {
8393

8494
const cancelRegister = registerWatch(store => {
8595
const newValue = getValue(store, namePathRef.current);
86-
if (valueCacheRef.current !== newValue) {
96+
const nextValueStr = stringify(newValue);
97+
98+
// Compare stringify in case it's nest object
99+
if (valueStrRef.current !== nextValueStr) {
87100
setValue(newValue);
88101
}
89102
});

tests/useWatch.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ describe('useWatch', () => {
275275
});
276276
expect(renderTime).toEqual(2);
277277
});
278+
278279
it('typescript', () => {
279280
type FieldType = {
280281
main?: string;
@@ -307,4 +308,46 @@ describe('useWatch', () => {
307308

308309
mount(<Demo />);
309310
});
311+
312+
// https://github.com/react-component/field-form/issues/431
313+
it('not trigger effect', () => {
314+
let updateA = 0;
315+
let updateB = 0;
316+
317+
const Demo = () => {
318+
const [form] = Form.useForm();
319+
const userA = Form.useWatch(['a'], form);
320+
const userB = Form.useWatch(['b'], form);
321+
322+
React.useEffect(() => {
323+
updateA += 1;
324+
console.log('Update A', userA);
325+
}, [userA]);
326+
React.useEffect(() => {
327+
updateB += 1;
328+
console.log('Update B', userB);
329+
}, [userB]);
330+
331+
return (
332+
<Form form={form}>
333+
<Field name={['a', 'name']}>
334+
<Input />
335+
</Field>
336+
<Field name={['b', 'name']}>
337+
<Input />
338+
</Field>
339+
</Form>
340+
);
341+
};
342+
343+
const wrapper = mount(<Demo />);
344+
345+
console.log('Change!');
346+
wrapper
347+
.find('input')
348+
.first()
349+
.simulate('change', { target: { value: 'bamboo' } });
350+
351+
expect(updateA > updateB).toBeTruthy();
352+
});
310353
});

0 commit comments

Comments
 (0)