Is it only me who think that the Modal APIs provided by popular frameworks such as antd (https://ant.design/components/modal/) are awkward and cumbersome to use?
It’s just not intuitive (like confirm()
or prompt('input a number')
), and how do you show multiple instances of a same type of modal?
I have a trick to display modals in an imperative way.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { Button, Modal } from 'antd';
/*
class Modal extends React.Component<any, any> {
static ModalTitle = 'TEST';
ModalButtons = (ctx: any) => [
<Button onClick={() => ctx.closeModal('123')}>a</Button>,
<span>a</span>,
];
render() {
return <div>modal</div>;
}
}
showModal(Modal, {}).then(r => console.log(r))
or
showModal(
UserInfoModal,
{ user: currentUser },
{
ModalTitle: 'Test',
ModalButtons: (ctx: any) => [
<Button onClick={() => ctx.closeModal('123')}>a</Button>,
<span>a</span>,
],
},
).then(console.log);
*/
function getModalButtons(ModalButtons: any, context: any) {
if (ModalButtons) {
return ModalButtons(context);
}
const { closeModal } = context;
return [
<Button autoFocus onClick={() => closeModal()}>
Close
</Button>,
];
}
function showModal(
ModalClass: any,
props: any = {},
config: { ModalTitle?: string; ModalButtons?: any } = {},
) {
return new Promise((resolve) => {
const modalRoot = document.createElement('div');
let clearUp: null | (() => void) = null;
document.body.appendChild(modalRoot);
const Root = () => {
const [open, setOpen] = useState(true);
const [modalButtons, setModalButtons] = useState(null);
const closeModalFunc = (resolution: any) => {
resolve(resolution);
setOpen(false);
if (clearUp) {
clearUp();
}
};
const modalInstance = React.createElement(ModalClass, {
...(props || {}),
// for non functional components
ref: (instance: any) => {
if (!instance || modalButtons) {
return;
}
setModalButtons(
getModalButtons(instance.ModalButtons, {
closeModal: closeModalFunc,
}),
);
},
});
return (
<Modal
visible={open}
onCancel={() => closeModalFunc(null)}
width={(ModalClass as any).ModalWidth || undefined}
title={(ModalClass as any).ModalTitle || config.ModalTitle || 'Modal'}
footer={modalButtons}
>
{modalInstance}
</Modal>
);
};
ReactDOM.render(<Root />, modalRoot);
clearUp = () => {
setTimeout(() => {
ReactDOM.unmountComponentAtNode(modalRoot);
try {
document.body.removeChild(modalRoot);
} catch (e) {
console.warn(e);
}
}, 1000);
};
});
}
export default showModal;
Actually, I discussed this idea with the author of github.com/eBay/nice-modal-react (before this repo was published), so you can use this library too.