fix(form): resolve the problem of validation states changing out of sequence (#41412)

* fix: reolve FormItem's validate status trigger unexpected issue

* feat: add test case

* feat: optimize code

* feat: optimize code

* feat: optimize code

* style: format code

* git

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: react version

* style: format code

* refactor: refactor code

* refactor: refactor code

* refactor: refactor code
This commit is contained in:
kiner-tang(文辉) 2023-03-23 16:40:53 +08:00 committed by GitHub
parent 6a2cad277e
commit bf8a36be8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 33 deletions

View File

@ -9,10 +9,10 @@ import omit from 'rc-util/lib/omit';
import * as React from 'react';
import type { FormItemProps, ValidateStatus } from '.';
import { Row } from '../../grid';
import type { FormItemStatusContextProps, ReportMetaChange } from '../context';
import { FormContext, FormItemInputContext, NoStyleItemContext } from '../context';
import FormItemInput from '../FormItemInput';
import FormItemLabel from '../FormItemLabel';
import type { FormItemStatusContextProps, ReportMetaChange } from '../context';
import { FormContext, FormItemInputContext, NoStyleItemContext } from '../context';
import useDebounce from '../hooks/useDebounce';
const iconMap = {
@ -81,29 +81,38 @@ export default function ItemHolder(props: ItemHolderProps) {
};
// ======================== Status ========================
let mergedValidateStatus: ValidateStatus = '';
const getValidateState = (isDebounce = false) => {
let status: ValidateStatus = '';
const _errors = isDebounce ? debounceErrors : meta.errors;
const _warnings = isDebounce ? debounceWarnings : meta.warnings;
if (validateStatus !== undefined) {
mergedValidateStatus = validateStatus;
status = validateStatus;
} else if (meta.validating) {
mergedValidateStatus = 'validating';
} else if (debounceErrors.length) {
mergedValidateStatus = 'error';
} else if (debounceWarnings.length) {
mergedValidateStatus = 'warning';
status = 'validating';
} else if (_errors.length) {
status = 'error';
} else if (_warnings.length) {
status = 'warning';
} else if (meta.touched || (hasFeedback && meta.validated)) {
// success feedback should display when pass hasFeedback prop and current value is valid value
mergedValidateStatus = 'success';
status = 'success';
}
return status;
};
const mergedValidateStatus = getValidateState();
const formItemStatusContext = React.useMemo<FormItemStatusContextProps>(() => {
let feedbackIcon: React.ReactNode;
const desplayValidateStatus = getValidateState(true);
if (hasFeedback) {
const IconNode = mergedValidateStatus && iconMap[mergedValidateStatus];
const IconNode = desplayValidateStatus && iconMap[desplayValidateStatus];
feedbackIcon = IconNode ? (
<span
className={classNames(
`${itemPrefixCls}-feedback-icon`,
`${itemPrefixCls}-feedback-icon-${mergedValidateStatus}`,
`${itemPrefixCls}-feedback-icon-${desplayValidateStatus}`,
)}
>
<IconNode />

View File

@ -1,30 +1,30 @@
import type { ChangeEventHandler } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import classNames from 'classnames';
import type { ColProps } from 'antd/es/grid';
import classNames from 'classnames';
import type { ChangeEventHandler } from 'react';
import React, { version as ReactVersion, useEffect, useRef, useState } from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import type { FormInstance } from '..';
import Form from '..';
import * as Util from '../util';
import Button from '../../button';
import Input from '../../input';
import Select from '../../select';
import Upload from '../../upload';
import Cascader from '../../cascader';
import Checkbox from '../../checkbox';
import DatePicker from '../../date-picker';
import InputNumber from '../../input-number';
import Radio from '../../radio';
import Switch from '../../switch';
import TreeSelect from '../../tree-select';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, pureRender, render, screen, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button';
import Cascader from '../../cascader';
import Checkbox from '../../checkbox';
import ConfigProvider from '../../config-provider';
import DatePicker from '../../date-picker';
import Drawer from '../../drawer';
import Input from '../../input';
import InputNumber from '../../input-number';
import zhCN from '../../locale/zh_CN';
import Modal from '../../modal';
import Radio from '../../radio';
import Select from '../../select';
import Switch from '../../switch';
import TreeSelect from '../../tree-select';
import Upload from '../../upload';
import type { NamePath } from '../interface';
import * as Util from '../util';
const { RangePicker } = DatePicker;
const { TextArea } = Input;
@ -1626,4 +1626,51 @@ describe('Form', () => {
expect(container.querySelectorAll('.ant-form-item-has-feedback').length).toBe(1);
expect(container.querySelectorAll('.ant-form-item-has-success').length).toBe(1);
});
it('validate status should be change in order', async () => {
const onChange = jest.fn();
const CustomInput = (props: any) => {
const { status } = Form.Item.useStatus();
useEffect(() => {
onChange(status);
}, [status]);
return <Input {...props} />;
};
const App = () => (
<Form>
<Form.Item>
<Form.Item name="test" label="test" rules={[{ len: 3, message: 'error.' }]}>
<CustomInput />
</Form.Item>
</Form.Item>
</Form>
);
render(<App />);
await waitFakeTimer();
// initial validate
const initTriggerTime = ReactVersion.startsWith('18') ? 2 : 1;
expect(onChange).toHaveBeenCalledTimes(initTriggerTime);
let idx = 1;
expect(onChange).toHaveBeenNthCalledWith(idx++, '');
if (initTriggerTime === 2) {
expect(onChange).toHaveBeenNthCalledWith(idx++, '');
}
// change trigger
await changeValue(0, '1');
expect(onChange).toHaveBeenCalledTimes(initTriggerTime + 2);
expect(onChange).toHaveBeenNthCalledWith(idx++, 'validating');
expect(onChange).toHaveBeenNthCalledWith(idx++, 'error');
await changeValue(0, '11');
expect(onChange).toHaveBeenCalledTimes(initTriggerTime + 4);
expect(onChange).toHaveBeenNthCalledWith(idx++, 'validating');
expect(onChange).toHaveBeenNthCalledWith(idx++, 'error');
await changeValue(0, '111');
expect(onChange).toHaveBeenCalledTimes(initTriggerTime + 6);
expect(onChange).toHaveBeenNthCalledWith(idx++, 'validating');
expect(onChange).toHaveBeenNthCalledWith(idx++, 'success');
});
});