Skip to content

Commit 982293e

Browse files
committed
fix(form): prevent useWatch from triggering twice during Form.List updates
1 parent 3247c62 commit 982293e

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

src/useForm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ export class FormStore {
775775
type: 'valueUpdate',
776776
source: 'internal',
777777
});
778-
this.notifyWatch([namePath]);
778+
this.batchNotifyWatch(namePath);
779779

780780
// Dependencies update
781781
const childrenFields = this.triggerDependenciesUpdate(prevStore, namePath);

tests/useWatch.test.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,4 +536,69 @@ describe('useWatch', () => {
536536
expect(list[0]).toEqual({});
537537
expect(list[1]).toEqual({ name: 'bamboo' });
538538
});
539+
540+
it('list remove should not trigger intermediate undefined value', async () => {
541+
const snapshots: any[] = [];
542+
543+
const Demo: React.FC = () => {
544+
const [form] = Form.useForm();
545+
const users = Form.useWatch<string[]>(['users'], form) || [];
546+
547+
React.useEffect(() => {
548+
snapshots.push(users);
549+
}, [users]);
550+
551+
return (
552+
<Form form={form} style={{ border: '1px solid red', padding: 15 }}>
553+
<div className="values">{JSON.stringify(users)}</div>
554+
<List name="users" initialValue={['bamboo', 'light']}>
555+
{(fields, { remove }) => (
556+
<div>
557+
{fields.map((field, index) => (
558+
<Field {...field} key={field.key} rules={[{ required: true }]}>
559+
{control => (
560+
<div>
561+
<Input {...control} />
562+
<a className="remove" onClick={() => remove(1)} />
563+
</div>
564+
)}
565+
</Field>
566+
))}
567+
</div>
568+
)}
569+
</List>
570+
</Form>
571+
);
572+
};
573+
574+
const { container } = render(<Demo />);
575+
576+
await act(async () => {
577+
await timeout();
578+
});
579+
580+
// Initial
581+
expect(container.querySelector<HTMLDivElement>('.values')?.textContent).toEqual(
582+
JSON.stringify(['bamboo', 'light']),
583+
);
584+
585+
// Remove index 1
586+
fireEvent.click(container.querySelector<HTMLAnchorElement>('.remove')!);
587+
588+
await act(async () => {
589+
await timeout();
590+
});
591+
592+
// Final
593+
expect(container.querySelector<HTMLDivElement>('.values')?.textContent).toEqual(
594+
JSON.stringify(['bamboo']),
595+
);
596+
597+
// Should not have intermediate state like ['bamboo', undefined]
598+
expect(
599+
snapshots.some(
600+
v => Array.isArray(v) && v.length === 2 && v[0] === 'bamboo' && v[1] === undefined,
601+
),
602+
).toBe(false);
603+
});
539604
});

0 commit comments

Comments
 (0)