@cmckenna/use-async-modal
v2.0.0
Published
Show promised based modal imperatively using hook for React.js.
Maintainers
Readme
@cmckenna/use-async-modal
Show promised based modal imperatively using hook for React.js.
About
This is basically the same repo as Harasz/use-async-modal except now you can pass props to the modal upon opening! :sunglasses:
Table of Contents
See CHANGELOG.md for release notes. 2.0.0 requires React 18+
and ships a dual ESM/CJS build; if you are on React 16.8–17, stay on 1.0.x.
Requirements
- React 18 or newer (
reactandreact-domare peer dependencies). - Node 18 or newer to consume the published package. The build output is ES2020 and uses no Node APIs.
(Working on this repo needs a newer Node than consuming it does — see Contributing.)
The package ships both ESM and CommonJS builds behind an exports map, so
import and require both resolve to the right entry with no bundler
configuration.
Using React 16.8–17? Stay on
1.0.x.
Installation
You can install package using npm or yarn:
npm i @cmckenna/use-async-modal
yarn add @cmckenna/use-async-modalUsage
First, we will need a component modal to show:
export const Dialog = ({ onResolve, title, acceptBtnText, cancelBtnText }) => {
return (
<div>
<button onClick={() => onResolve({ accepted: true })}>{acceptBtnText ?? 'Accept'}</button>
<button onClick={() => onResolve({ accepted: false })}>{cancelBtnText ?? 'Cancel'}</button>
</div>
);
};Then in the component we want to open a modal, we need to use useModal hook from @cmckenna/use-async-modal package. It is necessary to add only once the ModalContainer component from @cmckenna/use-async-modal package as high as possible in your app.
import { useModal, ModalContainer } from "@cmckenna/use-async-modal";
import { Dialog } from "./Dialog";
export const App = () => {
const showModal = useModal({
Component: Dialog,
/*
Type: Function
Desc: It will be invoked when the window is closed.
Options:
- resolved: value passed to onResolve.
*/
onClose: ({ resolved }) => {},
/*
Type: Function
Desc: It will be invoked when the window is opened.
Options:
- containerId: string containing attribute id for overlay in DOM,
- containerRef: HTMLDivElement ref to overlay.
*/
onOpen: ({ containerId, containerRef }) => {},
/*
Type: Object
Desc: Inline styles applied to overlay.
*/
overlayStyles: {
backgroundColor: "red",
margin: "1px"
},
/*
Type: String
Desc: Classes to be applied to overlay.
Accept multiple classes names separated
by space ex. "px-1 mx-2 bg-green".
*/
overlayClassName: "px-1",
/*
Type: Bool
Desc: If set to true, dialog will close
after escape key down.
Default value: false
*/
closeOnEsc: true,
/*
Type: Bool
Desc: If set to true, the dialog will
close when the overlay was clicked.
Default value: false
*/
closeOnOverlayClick: true,
/*
Type: same as onResolve
Desc: The value passed to the onResolve
function when the dialog was closed with
the Escape key or an overlay click.
*/
defaultResolved: { accepted: false },
/*
Type: String
Desc: Classes to be applied to overlay
after closing. Accept multiple classes
names separated by space.
*/
overlayClassNameOnClose: "fade-out",
/*
Type: same as onResolve
Desc: Classes to be applied to overlay
when open. Accept multiple classes names
separated by space.
*/
overlayClassNameOnOpen: "fade-in",
/*
Type: Number (unit: ms)
Desc: Delayed closing time expressed
in milliseconds.
*/
closeTimeoutMs: 1000,
/*
Type: Bool (default: true)
Desc: Locking the scrollbar on the page
when the model is open.
*/
blockBodyScroll: true,
});
async function handleClick() {
const status = await showModal({
title: 'Hello World',
acceptBtnText: 'Ok',
cancelBtnText: 'Deny'
});
// { accepted: true } or { accepted: false }
}
/*
necessary to add <ModalContainer /> once at the top of app
*/
return (
<>
<ModalContainer />
<button onClick={handleClick}>Open dialog</button>
</>
);
};
Styles
The overlay ships with a small default stylesheet. Import it once, anywhere in your app:
import "@cmckenna/use-async-modal/index.css";You can skip it entirely and style .useModal__overlay yourself, or override it
per-modal with the overlayStyles / overlayClassName options.
As a hook argument we pass an object with properties Component which is our modal component. showModal is a function that return promise with our value passed to function onResolve in Dialog component.
More examples of usage.
Demos
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Developing this repo needs a newer Node than consuming the published package
does. The dev toolchain's combined engines constraint is:
^22.22.2 || ^24.15.0 || >=26.0.0Note that this is not a continuous "22.22.2 and up" range — Node 23,
Node 24.0–24.14, and Node 25 are all excluded. jsdom 30 is the binding
constraint; Vite 8 (^20.19.0 || >=22.12.0) is looser.
.nvmrc pins a known-good version, so nvm use in this directory puts you on
a supported one. CI resolves the same file via node-version-file.
npm ci
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # vitest
npm run build # tsup -> dist/
npm run check:exports # publint + arethetypeswrongPlease make sure to update tests as appropriate.
