void;
+}
+
+export default function ErrorList({
+ errors = EMPTY_LIST,
+ help,
+ onDomErrorVisibleChange,
+}: ErrorListProps) {
+ const forceUpdate = useForceUpdate();
+ const { prefixCls, status } = React.useContext(FormItemPrefixContext);
+
+ const [visible, cacheErrors] = useCacheErrors(
+ errors,
+ changedVisible => {
+ if (changedVisible) {
+ /**
+ * We trigger in sync to avoid dom shaking but this get warning in react 16.13.
+ * So use Promise to keep in micro async to handle this.
+ * https://github.com/ant-design/ant-design/issues/21698#issuecomment-593743485
+ */
+ Promise.resolve().then(() => {
+ onDomErrorVisibleChange?.(true);
+ });
+ }
+ forceUpdate();
+ },
+ !!help,
+ );
+
+ const memoErrors = useMemo(
+ () => cacheErrors,
+ visible,
+ (_, nextVisible) => nextVisible,
+ );
+
+ // Memo status in same visible
+ const [innerStatus, setInnerStatus] = React.useState(status);
+ React.useEffect(() => {
+ if (visible && status) {
+ setInnerStatus(status);
+ }
+ }, [visible, status]);
+
+ const baseClassName = `${prefixCls}-item-explain`;
+
+ return (
+
{
+ onDomErrorVisibleChange?.(false);
+ }}
+ motionAppear
+ removeOnLeave
+ >
+ {({ className: motionClassName }: { className: string }) => {
+ return (
+
+ {memoErrors.map((error, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ {error}
+
+ ))}
+
+ );
+ }}
+
+ );
+}
diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx
index 3a7c9ead7e..e899b4d7a3 100644
--- a/components/form/FormItem.tsx
+++ b/components/form/FormItem.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { useContext, useRef } from 'react';
import isEqual from 'lodash/isEqual';
import classNames from 'classnames';
import { Field, FormInstance } from 'rc-field-form';
@@ -86,15 +87,14 @@ function FormItem(props: FormItemProps): React.ReactElement {
hidden,
...restProps
} = props;
- const destroyRef = React.useRef(false);
- const { getPrefixCls } = React.useContext(ConfigContext);
- const { name: formName, requiredMark } = React.useContext(FormContext);
- const { updateItemErrors } = React.useContext(FormItemContext);
+ const destroyRef = useRef(false);
+ const { getPrefixCls } = useContext(ConfigContext);
+ const { name: formName, requiredMark } = useContext(FormContext);
+ const { updateItemErrors } = useContext(FormItemContext);
const [domErrorVisible, innerSetDomErrorVisible] = React.useState(!!help);
- const prevValidateStatusRef = React.useRef
(validateStatus);
const [inlineErrors, setInlineErrors] = useFrameState>({});
- const { validateTrigger: contextValidateTrigger } = React.useContext(FieldContext);
+ const { validateTrigger: contextValidateTrigger } = useContext(FieldContext);
const mergedValidateTrigger =
validateTrigger !== undefined ? validateTrigger : contextValidateTrigger;
@@ -107,7 +107,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
const hasName = hasValidName(name);
// Cache Field NamePath
- const nameRef = React.useRef<(string | number)[]>([]);
+ const nameRef = useRef<(string | number)[]>([]);
// Should clean up if Field removed
React.useEffect(() => {
@@ -176,10 +176,6 @@ function FormItem(props: FormItemProps): React.ReactElement {
mergedValidateStatus = 'success';
}
- if (domErrorVisible && help) {
- prevValidateStatusRef.current = mergedValidateStatus;
- }
-
const itemClassName = {
[`${prefixCls}-item`]: true,
[`${prefixCls}-item-with-help`]: domErrorVisible || help,
@@ -190,8 +186,6 @@ function FormItem(props: FormItemProps): React.ReactElement {
[`${prefixCls}-item-has-success`]: mergedValidateStatus === 'success',
[`${prefixCls}-item-has-warning`]: mergedValidateStatus === 'warning',
[`${prefixCls}-item-has-error`]: mergedValidateStatus === 'error',
- [`${prefixCls}-item-has-error-leave`]:
- !help && domErrorVisible && prevValidateStatusRef.current === 'error',
[`${prefixCls}-item-is-validating`]: mergedValidateStatus === 'validating',
[`${prefixCls}-item-hidden`]: hidden,
};
@@ -239,6 +233,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
{...meta}
errors={mergedErrors}
prefixCls={prefixCls}
+ status={mergedValidateStatus}
onDomErrorVisibleChange={setDomErrorVisible}
validateStatus={mergedValidateStatus}
>
@@ -253,7 +248,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
const isRenderProps = typeof children === 'function';
// Record for real component render
- const updateRef = React.useRef(0);
+ const updateRef = useRef(0);
updateRef.current += 1;
if (!hasName && !isRenderProps && !dependencies) {
diff --git a/components/form/FormItemInput.tsx b/components/form/FormItemInput.tsx
index 9ba28eb39a..ff134a93ba 100644
--- a/components/form/FormItemInput.tsx
+++ b/components/form/FormItemInput.tsx
@@ -4,14 +4,11 @@ import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
-import useMemo from 'rc-util/lib/hooks/useMemo';
-import CSSMotion from 'rc-motion';
import Col, { ColProps } from '../grid/col';
import { ValidateStatus } from './FormItem';
-import { FormContext } from './context';
-import useCacheErrors from './hooks/useCacheErrors';
-import useForceUpdate from '../_util/hooks/useForceUpdate';
+import { FormContext, FormItemPrefixContext } from './context';
+import ErrorList from './ErrorList';
interface FormItemInputMiscProps {
prefixCls: string;
@@ -26,6 +23,7 @@ export interface FormItemInputProps {
wrapperCol?: ColProps;
help?: React.ReactNode;
extra?: React.ReactNode;
+ status?: ValidateStatus;
}
const iconMap: { [key: string]: any } = {
@@ -37,6 +35,7 @@ const iconMap: { [key: string]: any } = {
const FormItemInput: React.FC = ({
prefixCls,
+ status,
wrapperCol,
children,
help,
@@ -46,8 +45,6 @@ const FormItemInput: React.FC = ({
validateStatus,
extra,
}) => {
- const forceUpdate = useForceUpdate();
-
const baseClassName = `${prefixCls}-item`;
const formContext = React.useContext(FormContext);
@@ -56,24 +53,6 @@ const FormItemInput: React.FC = ({
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.className);
- const [visible, cacheErrors] = useCacheErrors(
- errors,
- changedVisible => {
- if (changedVisible) {
- /**
- * We trigger in sync to avoid dom shaking but this get warning in react 16.13.
- * So use Promise to keep in micro async to handle this.
- * https://github.com/ant-design/ant-design/issues/21698#issuecomment-593743485
- */
- Promise.resolve().then(() => {
- onDomErrorVisibleChange(true);
- });
- }
- forceUpdate();
- },
- !!help,
- );
-
React.useEffect(
() => () => {
onDomErrorVisibleChange(false);
@@ -81,12 +60,6 @@ const FormItemInput: React.FC = ({
[],
);
- const memoErrors = useMemo(
- () => cacheErrors,
- visible,
- (_, nextVisible) => nextVisible,
- );
-
// Should provides additional icon if `hasFeedback`
const IconNode = validateStatus && iconMap[validateStatus];
const icon =
@@ -108,29 +81,13 @@ const FormItemInput: React.FC = ({
{children}
{icon}
}
diff --git a/components/form/FormList.tsx b/components/form/FormList.tsx
index bb346470c4..140242edca 100644
--- a/components/form/FormList.tsx
+++ b/components/form/FormList.tsx
@@ -2,6 +2,8 @@ import * as React from 'react';
import { List } from 'rc-field-form';
import { StoreValue } from 'rc-field-form/lib/interface';
import devWarning from '../_util/devWarning';
+import { ConfigContext } from '../config-provider';
+import { FormItemPrefixContext } from './context';
export interface FormListFieldData {
name: number;
@@ -16,19 +18,38 @@ export interface FormListOperation {
}
export interface FormListProps {
+ prefixCls?: string;
name: string | number | (string | number)[];
- children: (fields: FormListFieldData[], operation: FormListOperation) => React.ReactNode;
+ children: (
+ fields: FormListFieldData[],
+ operation: FormListOperation,
+ meta: { errors: React.ReactNode[] },
+ ) => React.ReactNode;
}
-const FormList: React.FC