diff --git a/src/useForm.ts b/src/useForm.ts index 7879cf85..f36e0b10 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -38,6 +38,7 @@ import { matchNamePath, setValue, } from './utils/valueUtil'; +import isEqual from 'rc-util/lib/isEqual'; type InvalidateFieldEntity = { INVALIDATE_NAME_PATH: InternalNamePath }; @@ -580,15 +581,40 @@ export class FormStore { const { name, ...data } = fieldData; const namePath = getNamePath(name); namePathList.push(namePath); + const hasValue = 'value' in data; + + const previousFieldEntity = this.getFieldEntitiesForNamePathList(namePath)[0] + let previousFieldMeta: Meta | null = null; + + if (previousFieldEntity && !('INVALIDATE_NAME_PATH' in previousFieldEntity)) { + previousFieldMeta = previousFieldEntity.getMeta(); + } + + let mergeTouched: boolean | undefined = previousFieldMeta?.touched + + if ('touched' in data) { + mergeTouched = data.touched; + } else if (hasValue && previousFieldMeta !== null) { + mergeTouched = !isEqual(this.getFieldValue(namePath), data.value) + } + + // Meta + const nextFieldMeta: Meta = { + ...previousFieldMeta, + ...(typeof mergeTouched === 'boolean' ? { touched: mergeTouched } : {}) + }; // Value - if ('value' in data) { + if (hasValue) { this.updateStore(setValue(this.store, namePath, data.value)); } this.notifyObservers(prevStore, [namePath], { type: 'setField', - data: fieldData, + data: { + ...nextFieldMeta, + ...fieldData, + }, }); }); diff --git a/tests/index.test.tsx b/tests/index.test.tsx index a9a0e740..d4deaccb 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -952,7 +952,7 @@ describe('Form.Basic', () => { }); }); - it('setFieldValue should always set touched', async () => { + it('setFieldsValue should always set touched', async () => { const EMPTY_VALUES = { light: '', bamboo: [] }; const formRef = React.createRef(); @@ -997,6 +997,35 @@ describe('Form.Basic', () => { expect(formRef.current?.getFieldError('bamboo')).toHaveLength(0); }); + // https://github.com/ant-design/ant-design/issues/53981 + it('setFieldValue should mark the registered fields as touched', async () => { + const formRef = React.createRef(); + + const Demo: React.FC = () => ( +
+ + + +
+ ); + + render(); + + // Mock error first + await act(async () => { + await formRef.current?.validateFields().catch(() => {}); + }); + expect(formRef.current?.getFieldError('light')).toHaveLength(1); + expect(formRef.current?.isFieldTouched('light')).toBeFalsy(); + + await act(async () => { + formRef.current?.setFieldValue('light', 'Bamboo'); + await Promise.resolve(); + }); + expect(formRef.current?.getFieldError('light')).toHaveLength(0); + expect(formRef.current?.isFieldTouched('light')).toBeTruthy(); + }) + it('setFieldValue should reset errors', async () => { const formRef = React.createRef();