react-call v2

Nested dialog

A Callable that opens itself. Each open instance can spawn the same Callable from inside its own JSX — the library tracks the stack and resolves each promise independently.

Nested

The Callable

declared once, mounted in the React tree
NestedDialog.tsx
import { createCallable } from 'react-call'
const DOM_LIMIT = 10
interface Props {
level: number
}
export const NestedDialog = createCallable<Props, void>(({ call, level }) => {
// Keep older instances in the Stack so closing the top falls back to
// them, but skip rendering once they're more than DOM_LIMIT below the
// top — the DOM doesn't need to hold hundreds of hidden dialogs.
if (call.index < call.stackSize - DOM_LIMIT) return null
const isTopmost = call.index + 1 === call.stackSize
const openNested = () => NestedDialog.call({ level: level + 1 })
// Loop the cascade after a few steps so the card never marches off-screen.
const offset = (call.index % 6) * 18
return (
<div
className={
isTopmost
? 'fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm'
: 'pointer-events-none fixed inset-0 z-50 flex items-center justify-center'
}
>
<div
className="pointer-events-auto w-[340px] rounded-lg border border-[var(--color-border)] bg-[var(--color-bg)] p-6 shadow-2xl"
style={{ transform: `translate(${offset}px, ${offset}px)` }}
>
<div className="flex items-center justify-between">
<p className="font-mono text-[10px] uppercase tracking-wider text-[var(--color-fg-subtle)]">
Level {level} · #{call.index + 1} of {call.stackSize}
</p>
<button
type="button"
onClick={() => call.end()}
aria-label="Close"
className="-mr-1 inline-flex h-7 w-7 items-center justify-center rounded-md text-base leading-none text-[var(--color-fg-subtle)] transition-colors hover:bg-[var(--color-bg-subtle)] hover:text-[var(--color-fg)]"
>
×
</button>
</div>
<p className="mt-3 text-sm text-[var(--color-fg)]">
A Callable can open itself. Each open instance has its own promise,
resolved by its own{' '}
<code className="font-mono text-xs">call.end()</code>.
</p>
<div className="mt-5 flex items-center justify-between gap-3">
<button
type="button"
onClick={() => NestedDialog.end()}
className="rounded-md text-sm font-medium text-[var(--color-fg-subtle)] transition-colors hover:text-[var(--color-fg-muted)]"
>
Close all
</button>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => call.end()}
className="rounded-md border border-[var(--color-border)] px-4 py-2 text-sm font-medium text-[var(--color-fg-muted)] transition-colors hover:text-[var(--color-fg)]"
>
Close
</button>
<button
type="button"
onClick={openNested}
className="rounded-md bg-[var(--color-accent)] px-4 py-2 text-sm font-medium text-[var(--color-accent-fg)] transition-colors hover:bg-[var(--color-accent-hover)]"
>
Open nested
</button>
</div>
</div>
</div>
</div>
)
})
NestedDialog.displayName = 'NestedDialog'

The Root

mounted in your app tree, once
App.tsx
import { NestedDialog } from './NestedDialog'
import { OpenNestedButton } from './OpenNestedButton'
export default function App() {
return (
<>
<NestedDialog />
<OpenNestedButton />
</>
)
}

The caller

anywhere in your app, imperative
OpenNestedButton.tsx
import { NestedDialog } from './NestedDialog'
export const OpenNestedButton = () => (
<button
type="button"
onClick={() => NestedDialog.call({ level: 1 })}
className="rounded-md bg-[var(--color-accent)] px-4 py-2 text-sm font-medium text-[var(--color-accent-fg)] transition-colors hover:bg-[var(--color-accent-hover)]"
>
Open dialog
</button>
)

Recursion is free

A Callable component is just a React component. From inside its JSX, you can call Callable.call(props) exactly like any other consumer — that spawns another instance of the same Callable, with its own props and its own promise.

The library’s Root walks its Stack and renders each open call. They live side-by-side; closing one doesn’t touch the others.

What call.index and call.stackSize give you

The header reads “Level N · #i of M”:

  • level is a plain prop the caller passes — it survives nesting because each child opens with level + 1.
  • call.index is the position of this call in the Stack, from the CallContext. It’s zero-based.
  • call.stackSize is the total open count. It updates live as siblings open and close.

These let you build position-aware UI (cascade offset, “press Esc to close all”, “1 of 3 unsaved” etc.) without threading state through every intermediate render.

Closing them all at once

Callable.end(response) with no promise broadcasts to every open call. Here the Response is void, so NestedDialog.end() ends every level in one shot — the “Close all” button uses exactly that. The targeted form call.end() from CallContext still closes only the call it belongs to.

Virtualizing the DOM

Each open call mounts the same JSX, which gets expensive once the stack grows. The demo virtualizes:

if (call.index < call.stackSize - DOM_LIMIT) return null

Older instances stay in the Stack — so closing the top falls back to them — but they skip rendering until they’re within the topmost N. With DOM_LIMIT = 10, depth-100 nesting still renders only 10 dialogs.

Related examples