npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@paratco/async-modal

v2.1.3

Published

React async modal

Readme

React Async Modal

A lightweight, type-safe, promise-based modal system for React.

This package provides three different modal patterns:

  • modal.add() → Global modal system
  • ModalProvider → Context-based modal system
  • ImperativeModal → Ref-based modal system

Built with:

  • React Hooks
  • TypeScript
  • Async/Await API
  • useSyncExternalStore
  • Zero dependencies

Table of Contents


Features

  • Promise-based modal API
  • Fully typed modal data and responses
  • Multiple modal architectures
  • Animation support with outDelay
  • Lightweight and reusable
  • No reducers
  • No external state library
  • React 18 compatible
  • TypeScript support
  • Async/Await modal handling

Installation

Install with npm:

npm install @paratco/async-modal

Install with yarn:

yarn add @paratco/async-modal

Install with pnpm:

pnpm add @paratco/async-modal

Install with bun:

bun add @paratco/async-modal

Basic Modal Type

All modal systems use the same modal component contract.

type AsyncModalProps<Response, Data> = {
  isVisible: boolean;
  dismissible: boolean;
  data?: Data;
  onClose: (result?: Response) => void;
};

Create a Modal

import type { AsyncModalProps } from "@paratco/async-modal";
import type { ReactElement } from "react";

export interface DataProps {
  value: string;
}

export default function MyModal({ dismissible, isVisible, onClose, data }:
AsyncModalProps<boolean, DataProps>): ReactElement {
  const handleClose = (): void => {
    if (dismissible) {
      onClose(false);
    }
  };

  const handleConfirm = (): void => {
    onClose(false);
  };

  return (
    <div 
      style={{
        width: "100%",
        height: "100%",
        position: "fixed",
        left: 0,
        top: 0,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        backgroundColor: "rgba(0, 0, 0, 0.5)",
        padding: "12px",
        zIndex: 10,
        ...(isVisible === true ? {
          visibility: "visible",
          opacity: 1
        } : {
          visibility: "hidden",
          opacity: 0
        })
      }}
      onClick={() => {
        handleClose();
      }}
    >
      <div 
        style={{
          borderRadius: "12px",
          padding: "20px",
          display: "flex",
          flexDirection: "column",
          gap: "12px",
          backgroundColor: "white"
        }}
        onClick={(e) => {
          e.stopPropagation();
        }}
      >
        <p>
          This is Container Modal
          {
            data?.value
          }
        </p>

        <button
          type="button" 
          onClick={() => {
            onClose(false);
          }}
        >
          <p>
            Cancel
          </p>
        </button>
        <button type="button" onClick={handleConfirm}>
          <p>
            Confirm
          </p>
        </button>
      </div>
    </div>
  );
}

1. Usage Container modal

A global modal architecture using ModalContainer.

Best for:

  • App-wide dialogs
  • Global confirms
  • Notification modals

Setup

Render ModalContainer once in your application.

import { ModalContainer } from "@paratco/async-modal";

function MyComponent() {
  return (
    <>
      <ModalContainer />
    </>
  );
}

Open Modal

import { modal } from "@paratco/async-modal";
import { ConfirmModal } from "./ConfirmModal";

function MyComponent() {
  const showMyModal = async (): Promise<void> => {
    const result = await modal.add(
      MyModal
    , {
      data: {
        value: "this value is data this modal"
      },
      dismissible: true
    });

    console.log(`Container Modal result: ${result}`);
  };

  return (
    <>
      <ModalContainer />

      <Button
        variant="text"
        onClick={() => {
          void showMyModal();
        }}
      >
        Show Modal Container
      </Button>
    </>
  );
}

With Animation Delay

await modal.add(
  MyModal, 
  {
    outDelay: 300 //ms
  }
);

2. Usage Provider Modal

A provider-driven modal architecture using React Context.

Best for:

  • Feature-scoped modals
  • Modular applications
  • Context-driven apps

Setup

import { ModalProvider } from "@paratco/async-modal";

function App() {
  return (
    <ModalProvider>
      <HomePage />
    </ModalProvider>
  );
}

Open Modal

import { useProviderModal } from "@paratco/async-modal";
import { ConfirmModal } from "./ConfirmModal";

function App() {
  const { show } = useProviderModal();

  const showMyModal = async (): Promise<void> => {
    const result = await show(
      MyModal
      , {
        dismissible: true
      }
    );

    console.log(`Provider Modal result: ${result}`);
  };

  return(
    <button
      type="button"
      onClick={() => {
        void showMyModal();
      }}
    >
      Show Provider Modal
    </button>
  );
}

Default Provider Options

<ModalProvider
  dismissible={false}
  outDelay={500}
>
  <App />
</ModalProvider>

These values become default options for all modals inside the provider.


3. Usage Imperative Modal

A ref-based modal architecture.

Best for:

  • Local component workflows
  • Independent modal instances
  • Ref-based APIs

Setup

import { useRef } from "react";
import type { ReactElement } from "react";
import { Button } from "@mui/material";
import { ImperativeModal } from "@paratco/async-modal";
import type { ImperativeModalRef } from "@paratco/async-modal";
import MyModal from "./components/MyModal";

function App(): ReactElement {
  const ref = useRef<ImperativeModalRef<typeof MyModal>>(null);

  return (
    <React.Fragment>
      <ImperativeModal
        Modal={MyModal}
        ref={ref}
        dismissible={true}
      />

      <Button
        variant="contained"
        onClick={() => {
          void showModal();
        }}
      >
        Show Modal
      </Button>
    </React.Fragment>
  );
}

export default App;

Open Modal

const showModal = async (): Promise<void> => {
  const result = await ref.current?.api.show();

  console.log(`Imperative Modal result: ${result}`);
};

Full Imperative Example

import { useRef } from "react";
import type { ReactElement } from "react";
import { Button } from "@mui/material";
import { ImperativeModal } from "@paratco/async-modal";
import type { ImperativeModalRef } from "@paratco/async-modal";
import MyModal from "./components/MyModal";

function App(): ReactElement {
  const ref = useRef<ImperativeModalRef<typeof MyModal>>(null);

  const showModal = async (): Promise<void> => {
    const result = await ref.current?.api.show();

    console.log(`Imperative Modal result: ${result}`);
  };

  return (
    <React.Fragment>
      <ImperativeModal
        Modal={MyModal}
        ref={ref}
        dismissible={true}
      />

      <Button
        variant="contained"
        onClick={() => {
          void showModal();
        }}
      >
        Show Modal
      </Button>
    </React.Fragment>
  );
}

export default App;

Modal Options

All modal systems support the same options.

| Option | Type | Description | |---|---|---| | dismissible | boolean | Allow dismiss modal | | outDelay | number | Delay before unmount | | data | unknown | Modal input data |


Animation Support

Use outDelay for exit animations.

await modal.add(ConfirmModal, {
  outDelay: 300
});

Inside modal:

<div className={isVisible ? "fade-in" : "fade-out"}>
  Modal Content
</div>

Example CSS:

.fade-in {
  opacity: 1;
  transition: opacity 0.3s;
}

.fade-out {
  opacity: 0;
  transition: opacity 0.3s;
}

How It Works

modal.add()

  • Modal is stored globally
  • ModalContainer subscribes to updates
  • Modal renders dynamically
  • onClose() resolves Promise
  • Modal is removed automatically

ModalProvider

Custom Dismiss Behavior

  • show() updates provider state
  • Provider renders modal dynamically
  • Modal resolves Promise on close
  • Provider removes modal automatically

ImperativeModal

  • show() sets modal visible
  • Internal Promise is created
  • Modal resolves Promise on close
  • Visibility resets automatically

Architecture Comparison

| Feature | modal.add() | ModalProvider | ImperativeModal | |---|---|---|---| | Global state | ✅ | ❌ | ❌ | | Context-based | ❌ | ✅ | ❌ | | Ref-based | ❌ | ❌ | ✅ | | Scoped modals | ❌ | ✅ | ✅ | | Requires Provider | ❌ | ✅ | ❌ | | Requires Container | ✅ | ❌ | ❌ | | Best for app-wide dialogs | ✅ | ✅ | ❌ | | Best for feature modules | ❌ | ✅ | ✅ |


Best Practices

  • Keep modal components reusable
  • Use typed modal responses
  • Use outDelay for animations
  • Prefer ModalProvider for feature modules
  • Prefer modal.add() for global confirms
  • Prefer ImperativeModal for local workflows
  • Keep modal UI separated from business logic

Example With MUI

import {
  Dialog,
  DialogTitle,
  DialogActions,
  Button
} from "@mui/material";

import type {
  AsyncModalProps
} from "@paratco/async-modal";

type Response = boolean;

type Data = {
  title: string;
};

export function MuiConfirmModal(
  props: AsyncModalProps<Response, Data>
) {
  const {
    isVisible,
    data,
    onClose
  } = props;

  return (
    <Dialog
      open={isVisible}
      onClose={() => {
        onClose(false);
      }}
    >
      <DialogTitle>
        {data?.title}
      </DialogTitle>

      <DialogActions>
        <Button
          onClick={() => {
            onClose(false);
          }}
        >
          Cancel
        </Button>

        <Button
          onClick={() => {
            onClose(true);
          }}
        >
          Confirm
        </Button>
      </DialogActions>
    </Dialog>
  );
}