diff --git a/components/modal/ActionButton.tsx b/components/modal/ActionButton.tsx index 05e27d624533..24116616cbac 100644 --- a/components/modal/ActionButton.tsx +++ b/components/modal/ActionButton.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; import Button from '../button'; import { LegacyButtonType, ButtonProps, convertLegacyProps } from '../button/button'; @@ -11,40 +10,34 @@ export interface ActionButtonProps { buttonProps?: ButtonProps; } -export interface ActionButtonState { - loading: ButtonProps['loading']; -} - -export default class ActionButton extends React.Component { - timeoutId: number; - - clicked: boolean; +const ActionButton: React.FC = props => { + const clickedRef = React.useRef(false); + const ref = React.useRef(); + const [loading, setLoading] = React.useState(false); - state = { - loading: false, - }; - - componentDidMount() { - if (this.props.autoFocus) { - const $this = ReactDOM.findDOMNode(this) as HTMLInputElement; - this.timeoutId = setTimeout(() => $this.focus()); + React.useEffect(() => { + let timeoutId: number; + if (props.autoFocus) { + const $this = ref.current as HTMLInputElement; + timeoutId = setTimeout(() => $this.focus()); } - } - - componentWillUnmount() { - clearTimeout(this.timeoutId); - } + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, []); - handlePromiseOnOk(returnValueOfOnOk?: PromiseLike) { - const { closeModal } = this.props; + const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike) => { + const { closeModal } = props; if (!returnValueOfOnOk || !returnValueOfOnOk.then) { return; } - this.setState({ loading: true }); + setLoading(true); returnValueOfOnOk.then( (...args: any[]) => { // It's unnecessary to set loading=false, for the Modal will be unmounted after close. - // this.setState({ loading: false }); + // setState({ loading: false }); closeModal(...args); }, (e: Error) => { @@ -52,18 +45,18 @@ export default class ActionButton extends React.Component { - const { actionFn, closeModal } = this.props; - if (this.clicked) { + const onClick = () => { + const { actionFn, closeModal } = props; + if (clickedRef.current) { return; } - this.clicked = true; + clickedRef.current = true; if (!actionFn) { closeModal(); return; @@ -72,7 +65,7 @@ export default class ActionButton extends React.Component - {children} - - ); - } -} + const { type, children, buttonProps } = props; + return ( + + ); +}; + +export default ActionButton; diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx index 9adebe5ab010..c62b5de63612 100644 --- a/components/modal/Modal.tsx +++ b/components/modal/Modal.tsx @@ -9,7 +9,7 @@ import { getConfirmLocale } from './locale'; import Button from '../button'; import { LegacyButtonType, ButtonProps, convertLegacyProps } from '../button/button'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; -import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; let mousePosition: { x: number; y: number } | null; export const destroyFns: Array<() => void> = []; @@ -119,46 +119,41 @@ export interface ModalLocale { justOkText: string; } -export default class Modal extends React.Component { - static destroyAll: () => void; - - static useModal = useModal; +interface ModalInterface extends React.FC { + useModal: typeof useModal; +} - static defaultProps = { - width: 520, - transitionName: 'zoom', - maskTransitionName: 'fade', - confirmLoading: false, - visible: false, - okType: 'primary' as LegacyButtonType, - }; +const Modal: ModalInterface = props => { + const { getPopupContainer: getContextPopupContainer, getPrefixCls, direction } = React.useContext( + ConfigContext, + ); - handleCancel = (e: React.MouseEvent) => { - const { onCancel } = this.props; + const handleCancel = (e: React.MouseEvent) => { + const { onCancel } = props; if (onCancel) { onCancel(e); } }; - handleOk = (e: React.MouseEvent) => { - const { onOk } = this.props; + const handleOk = (e: React.MouseEvent) => { + const { onOk } = props; if (onOk) { onOk(e); } }; - renderFooter = (locale: ModalLocale) => { - const { okText, okType, cancelText, confirmLoading } = this.props; + const renderFooter = (locale: ModalLocale) => { + const { okText, okType, cancelText, confirmLoading } = props; return ( <> - @@ -166,54 +161,58 @@ export default class Modal extends React.Component { ); }; - renderModal = ({ - getPopupContainer: getContextPopupContainer, - getPrefixCls, - direction, - }: ConfigConsumerProps) => { - const { - prefixCls: customizePrefixCls, - footer, - visible, - wrapClassName, - centered, - getContainer, - closeIcon, - ...restProps - } = this.props; - - const prefixCls = getPrefixCls('modal', customizePrefixCls); - const defaultFooter = ( - - {this.renderFooter} - - ); + const { + prefixCls: customizePrefixCls, + footer, + visible, + wrapClassName, + centered, + getContainer, + closeIcon, + ...restProps + } = props; + + const prefixCls = getPrefixCls('modal', customizePrefixCls); + const defaultFooter = ( + + {renderFooter} + + ); + + const closeIconToRender = ( + + {closeIcon || } + + ); + + const wrapClassNameExtended = classNames(wrapClassName, { + [`${prefixCls}-centered`]: !!centered, + [`${prefixCls}-wrap-rtl`]: direction === 'rtl', + }); + return ( + + ); +}; - const closeIconToRender = ( - - {closeIcon || } - - ); - const wrapClassNameExtended = classNames(wrapClassName, { - [`${prefixCls}-centered`]: !!centered, - [`${prefixCls}-wrap-rtl`]: direction === 'rtl', - }); - return ( - - ); - }; +Modal.useModal = useModal; - render() { - return {this.renderModal}; - } -} +Modal.defaultProps = { + width: 520, + transitionName: 'zoom', + maskTransitionName: 'fade', + confirmLoading: false, + visible: false, + okType: 'primary' as LegacyButtonType, +}; + +export default Modal; diff --git a/components/modal/__tests__/Modal.test.js b/components/modal/__tests__/Modal.test.js index 9ec5562fa667..9902c13dceeb 100644 --- a/components/modal/__tests__/Modal.test.js +++ b/components/modal/__tests__/Modal.test.js @@ -52,15 +52,15 @@ describe('Modal', () => { it('onCancel should be called', () => { const onCancel = jest.fn(); - const wrapper = mount().instance(); - wrapper.handleCancel(); + const wrapper = mount(); + wrapper.find('.ant-btn').first().simulate('click'); expect(onCancel).toHaveBeenCalled(); }); it('onOk should be called', () => { const onOk = jest.fn(); - const wrapper = mount().instance(); - wrapper.handleOk(); + const wrapper = mount(); + wrapper.find('.ant-btn').last().simulate('click'); expect(onOk).toHaveBeenCalled(); }); diff --git a/components/modal/index.tsx b/components/modal/index.tsx index abaef9cfe382..f6adae2ec379 100644 --- a/components/modal/index.tsx +++ b/components/modal/index.tsx @@ -15,7 +15,7 @@ function modalWarn(props: ModalFuncProps) { return confirm(withWarn(props)); } -type Modal = typeof OriginModal & ModalStaticFunctions; +type Modal = typeof OriginModal & ModalStaticFunctions & { destroyAll: () => void }; const Modal = OriginModal as Modal; Modal.info = function infoFn(props: ModalFuncProps) {