|
|
|
import * as React from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
|
|
import CSSMotion from 'rc-motion';
|
|
|
|
import useMemo from 'rc-util/lib/hooks/useMemo';
|
|
|
|
import useCacheErrors from './hooks/useCacheErrors';
|
|
|
|
import useForceUpdate from '../_util/hooks/useForceUpdate';
|
|
|
|
import { FormItemPrefixContext } from './context';
|
|
|
|
import { ConfigContext } from '../config-provider';
|
|
|
|
|
|
|
|
const EMPTY_LIST: React.ReactNode[] = [];
|
|
|
|
|
|
|
|
export interface ErrorListProps {
|
|
|
|
errors?: React.ReactNode[];
|
|
|
|
/** @private Internal Usage. Do not use in your production */
|
|
|
|
help?: React.ReactNode;
|
|
|
|
/** @private Internal Usage. Do not use in your production */
|
|
|
|
onDomErrorVisibleChange?: (visible: boolean) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function ErrorList({
|
|
|
|
errors = EMPTY_LIST,
|
|
|
|
help,
|
|
|
|
onDomErrorVisibleChange,
|
|
|
|
}: ErrorListProps) {
|
|
|
|
const forceUpdate = useForceUpdate();
|
|
|
|
const { prefixCls, status } = React.useContext(FormItemPrefixContext);
|
|
|
|
const { getPrefixCls } = React.useContext(ConfigContext);
|
|
|
|
|
|
|
|
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`;
|
|
|
|
const rootPrefixCls = getPrefixCls();
|
|
|
|
|
|
|
|
return (
|
|
|
|
<CSSMotion
|
|
|
|
motionDeadline={500}
|
|
|
|
visible={visible}
|
|
|
|
motionName={`${rootPrefixCls}-show-help`}
|
|
|
|
onLeaveEnd={() => {
|
|
|
|
onDomErrorVisibleChange?.(false);
|
|
|
|
}}
|
|
|
|
motionAppear
|
|
|
|
removeOnLeave
|
|
|
|
>
|
|
|
|
{({ className: motionClassName }: { className?: string }) => (
|
|
|
|
<div
|
|
|
|
className={classNames(
|
|
|
|
baseClassName,
|
|
|
|
{
|
|
|
|
[`${baseClassName}-${innerStatus}`]: innerStatus,
|
|
|
|
},
|
|
|
|
motionClassName,
|
|
|
|
)}
|
|
|
|
key="help"
|
|
|
|
>
|
|
|
|
{memoErrors.map((error, index) => (
|
|
|
|
// eslint-disable-next-line react/no-array-index-key
|
|
|
|
<div key={index} role="alert">
|
|
|
|
{error}
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</CSSMotion>
|
|
|
|
);
|
|
|
|
}
|