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

use-imperative-portal

v1.1.2

Published

A minimal, imperative portal library for React & React Native.

Readme

use-imperative-portal

version minzip size license

A minimal, imperative portal library for React & React Native.

Control modals, toasts, and overlays from anywhere—inside or outside components—using standard Promises.

Key Features

  • Universal: Works on React Native & Web (Endpoint-based).
  • Hook-free: Call openPortal() from utility functions (e.g., API interceptors).
  • Async-first: Simply await portal.promise for user interaction.
  • Type-safe: Strictly typed updates for dynamic content.
  • Lightweight: Only 0.6kB (minzip). Zero boilerplate.

Install

npm i use-imperative-portal

Quick start

  1. Place <PortalEndpoint /> at the root (or anywhere you want portals to render).
import { PortalEndpoint } from 'use-imperative-portal'

export default function App() {
    return (
        <Provider>
            <MainContent />
            {/* Portals will be rendered here. Contexts (Theme, I18n) propagate naturally. */}
            <PortalEndpoint />
        </Provider>
    )
}
  1. Open a portal from anywhere.
import { openPortal } from 'use-imperative-portal'

// Can be a regular function, not just a component or hook!
export async function handleDelete() {
    // 1. Open
    const portal = openPortal<boolean>(
        <ConfirmDialog
            title="Delete this item?"
            onCancel={() => portal.close(false)}
            onConfirm={() => portal.close(true)}
        />
    )

    // 2. Wait
    const confirmed = await portal.promise
    if (!confirmed) return

    // 3. Process
    await api.deleteItem()
}

Examples

1. Await a result (Confirm / Prompt)

portal.close(value) resolves portal.promise with that value.

Confirm Dialog (Boolean)

import { openPortal } from 'use-imperative-portal'

export async function confirm(title: string) {
    const portal = openPortal<boolean>(() => (
        <Confirm title={title} onCancel={() => portal.close(false)} onOk={() => portal.close(true)} />
    ))
    return await portal.promise
}

// Usage: if (await confirm('Are you sure?')) ...

Prompt Input (Data)

export async function prompt(message: string) {
    const portal = openPortal<string | null>(() => (
        <Prompt message={message} onCancel={() => portal.close(null)} onSubmit={value => portal.close(value)} />
    ))
    return await portal.promise
}

// Usage: const name = await prompt('Your name?')

2. Type-safe updates (Progress bar)

Use a renderer function to enforce update arguments types.

import { openPortal } from 'use-imperative-portal'

// Define renderer: (number, string) => ReactNode
const renderer = (pct = 0, label = 'Ready') => (
    <div>
        <ProgressBar value={pct} />
        <span>{label}</span>
    </div>
)

export async function download() {
    const portal = openPortal(renderer)

    // portal.update(args) must match renderer's parameters
    portal.update(0, 'Starting...')

    for (let i = 0; i <= 100; i += 10) {
        await sleep(100)
        portal.update(i, 'Downloading...')
    }

    portal.close()
}

3. Auto-close with AbortSignal

If the signal is aborted, the portal closes automatically.

import { openPortal } from 'use-imperative-portal'

export async function upload(file: File) {
    const ac = new AbortController()
    const portal = openPortal(<div>Uploading…</div>, { signal: ac.signal })

    try {
        await doUpload(file, { signal: ac.signal })
    } finally {
        // Safe even if already closed by ac.abort()
        portal.close()
    }
}

API

openPortal(node, options?)

Returns a portal controller. The node argument determines how portal.update(...) works.

  • Static Node: openPortal(<div />)
    • portal.update(newNode) replaces content.
  • Renderer Function: openPortal((a, b) => <div />)
    • portal.update(a, b) calls the function with new args.
    • Use default parameters for the initial render (called with no args).
  • Options: { signal?: AbortSignal }

Portal<Result, UpdaterArgs>

  • portal.isClosed: boolean
  • portal.update(...args): Throws if closed.
  • portal.close(value?): Resolves promise. Safe to call multiple times.
  • portal.promise: Promise<Result>

PortalEndpoint

Renders all open portals where you place it in the tree. Contexts/styles propagate naturally.

createPortalContext()

Create separate “channels” (e.g. modals vs toasts).

import { createPortalContext } from 'use-imperative-portal'

const Toast = createPortalContext()
const Modal = createPortalContext()

export function Root() {
    return (
        <>
            <Toast.Endpoint />
            <App />
            <Modal.Endpoint />
        </>
    )
}

export function openDialog() {
    return Modal.openPortal(<Dialog />)
}

Notes

  • Lazy Evaluation: Renderer functions are evaluated lazily. This avoids TDZ issues and allows patterns like referencing portal inside the renderer closure.

License

MIT © skt-t1-byungi