mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-29 05:29:37 +08:00
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:
parent
6a2cad277e
commit
bf8a36be8f
@ -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 = '';
|
||||
if (validateStatus !== undefined) {
|
||||
mergedValidateStatus = validateStatus;
|
||||
} else if (meta.validating) {
|
||||
mergedValidateStatus = 'validating';
|
||||
} else if (debounceErrors.length) {
|
||||
mergedValidateStatus = 'error';
|
||||
} else if (debounceWarnings.length) {
|
||||
mergedValidateStatus = 'warning';
|
||||
} else if (meta.touched || (hasFeedback && meta.validated)) {
|
||||
// success feedback should display when pass hasFeedback prop and current value is valid value
|
||||
mergedValidateStatus = 'success';
|
||||
}
|
||||
|
||||
const getValidateState = (isDebounce = false) => {
|
||||
let status: ValidateStatus = '';
|
||||
const _errors = isDebounce ? debounceErrors : meta.errors;
|
||||
const _warnings = isDebounce ? debounceWarnings : meta.warnings;
|
||||
if (validateStatus !== undefined) {
|
||||
status = validateStatus;
|
||||
} else if (meta.validating) {
|
||||
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
|
||||
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 />
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user