@cmckenna/use-async-modal

中文文档
2.0.0发布于 2026/08/15MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 3563

Show promised based modal imperatively using hook for React.js.

原始使用文档
文档目录8 节
  1. This is basically the same repo as Harasz/use-async-modal e…
  2. Requirements
  3. Installation
  4. Usage
  5. Styles
  6. Demos
  7. Contributing
  8. License

@cmckenna/use-async-modal

Show promised based modal imperatively using hook for React.js.

npm NPM Test npm bundle size npm peer dependency version

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

  1. Requirements
  2. Installation
  3. Usage
  4. Demos
  5. Contributing
  6. License

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 (react and react-dom are 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-modal

Usage

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

Confirm dialog

Information dialog

Dialog with input

Animated dialog

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.0

Note 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 + arethetypeswrong

Please make sure to update tests as appropriate.

License

MIT

版本与发布记录

@cmckenna/use-async-modal 共发布 4 个版本,首次发布于 2023/10/26;过去 12 个月发布了 1 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.0.0latest2026/08/152 个月前
1.0.22023/10/263 年前
1.0.12023/10/263 年前
1.0.02023/10/263 年前

关于 @cmckenna/use-async-modal 的常见问题

@cmckenna/use-async-modal 是什么?

Show promised based modal imperatively using hook for React.js.

如何安装 @cmckenna/use-async-modal?

在项目目录运行 npm install @cmckenna/use-async-modal,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18)、react-dom(>=18)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cmckenna/use-async-modal 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cmckenna/use-async-modal 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cmckenna/use-async-modal 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cmckenna/use-async-modal 可以商用吗?

@cmckenna/use-async-modal 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cmckenna/use-async-modal 的下载量有多少?

根据 npm 官方下载统计,@cmckenna/use-async-modal 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,563 次,日均约 119 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cmckenna/use-async-modal 还在维护吗?

最新版本 2.0.0 发布于 2026/08/15(2 个月前);至今共发布 4 个版本,其中过去 12 个月发布了 1 个;首次发布于 2023/10/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。