mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-28 13:09:40 +08:00
25ea3a7202
* fix: Allow users to handle promise rejections in onOk callback Components that rely on ActionButton swallow rejected promises. This makes it impossible for userland code to handle them. * polish: Fix linting problems * polish: Return rejected promise instead of throwing * refact: Remove test for unhandled promise rejection This test breaks when run in parallel by Jest. At the moment we have no way of changing the way Jest works and it prohibits us from testing for "unhandledRejection" events. See: https://github.com/ant-design/ant-design/pull/40018#issuecomment-1373590259 * test: hack for rejection Co-authored-by: 二货机器人 <smith3816@gmail.com>
114 lines
3.0 KiB
TypeScript
114 lines
3.0 KiB
TypeScript
import useState from 'rc-util/lib/hooks/useState';
|
|
import * as React from 'react';
|
|
import Button from '../button';
|
|
import type { ButtonProps, LegacyButtonType } from '../button/button';
|
|
import { convertLegacyProps } from '../button/button';
|
|
|
|
export interface ActionButtonProps {
|
|
type?: LegacyButtonType;
|
|
actionFn?: (...args: any[]) => any | PromiseLike<any>;
|
|
close?: Function;
|
|
autoFocus?: boolean;
|
|
prefixCls: string;
|
|
buttonProps?: ButtonProps;
|
|
emitEvent?: boolean;
|
|
quitOnNullishReturnValue?: boolean;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
function isThenable(thing?: PromiseLike<any>): boolean {
|
|
return !!(thing && !!thing.then);
|
|
}
|
|
|
|
const ActionButton: React.FC<ActionButtonProps> = (props) => {
|
|
const clickedRef = React.useRef<boolean>(false);
|
|
const ref = React.useRef<HTMLInputElement>(null);
|
|
const [loading, setLoading] = useState<ButtonProps['loading']>(false);
|
|
const { close } = props;
|
|
const onInternalClose = (...args: any[]) => {
|
|
close?.(...args);
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
let timeoutId: NodeJS.Timer | null = null;
|
|
if (props.autoFocus) {
|
|
timeoutId = setTimeout(() => {
|
|
ref.current?.focus();
|
|
});
|
|
}
|
|
return () => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
|
|
if (!isThenable(returnValueOfOnOk)) {
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
returnValueOfOnOk!.then(
|
|
(...args: any[]) => {
|
|
setLoading(false, true);
|
|
onInternalClose(...args);
|
|
clickedRef.current = false;
|
|
},
|
|
(e: Error) => {
|
|
// See: https://github.com/ant-design/ant-design/issues/6183
|
|
setLoading(false, true);
|
|
clickedRef.current = false;
|
|
return Promise.reject(e);
|
|
},
|
|
);
|
|
};
|
|
|
|
const onClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
|
|
const { actionFn } = props;
|
|
if (clickedRef.current) {
|
|
return;
|
|
}
|
|
clickedRef.current = true;
|
|
if (!actionFn) {
|
|
onInternalClose();
|
|
return;
|
|
}
|
|
let returnValueOfOnOk: PromiseLike<any>;
|
|
if (props.emitEvent) {
|
|
returnValueOfOnOk = actionFn(e);
|
|
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
|
|
clickedRef.current = false;
|
|
onInternalClose(e);
|
|
return;
|
|
}
|
|
} else if (actionFn.length) {
|
|
returnValueOfOnOk = actionFn(close);
|
|
// https://github.com/ant-design/ant-design/issues/23358
|
|
clickedRef.current = false;
|
|
} else {
|
|
returnValueOfOnOk = actionFn();
|
|
if (!returnValueOfOnOk) {
|
|
onInternalClose();
|
|
return;
|
|
}
|
|
}
|
|
handlePromiseOnOk(returnValueOfOnOk);
|
|
};
|
|
|
|
const { type, children, prefixCls, buttonProps } = props;
|
|
return (
|
|
<Button
|
|
{...convertLegacyProps(type)}
|
|
onClick={onClick}
|
|
loading={loading}
|
|
prefixCls={prefixCls}
|
|
{...buttonProps}
|
|
ref={ref}
|
|
>
|
|
{children}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
export default ActionButton;
|