react-call
createCallable() turns a React component into something you can await: you
call it imperatively from anywhere and it resolves with a value. This skill
covers react-call v2.x — check the consumer's package.json and defer to the
installed version if it differs.
Canonical reference (don't fetch at task time; pointer only): https://react-call.desko.dev
When to reach for react-call (and when not)
Reach for it when a piece of UI conceptually returns a value to its caller
and you want to await that value from async code: confirmations, dialogs, form
modals, toasts/notifications, context menus, pickers, multi-step wizards.
Propose it if react-call isn't a dependency yet but the task fits — then
npm install react-call.
Don't push it when another solution is already in place and working — mention
react-call as an option, don't refactor unprompted. Skip it for purely
presentational components that return nothing, and for full-page flows better
served by routing.
Vocabulary (use these exact terms)
- Callable — the value
createCallable() returns. It is both a React
component (mount <Confirm />) and a namespace of methods (call, upsert,
end, update). Don't call it a "modal/dialog/component".
- Root — the mounting form of the Callable: the bare
<Confirm />. Not a
"provider/portal/outlet".
- Call — one imperative invocation (
Confirm.call({...})), resolves to a
Response.
- Stack — the ordered list of active Calls a Root renders (not a "queue").
- CallContext — the
call prop your component receives: { end, ended, key, index, stackSize, root }. Not a React "Context".
- Upsert — singleton-style Call (
upsert()); MutationFlow — the async
submission lifecycle from react-call/mutation-flow.
The model: Declare → Root → Call
import { createCallable } from 'react-call'
interface Props { message: string }
type Response = boolean
// 1. Declare — `call` is the special prop (the CallContext)
export const Confirm = createCallable<Props, Response>(({ call, message }) => (
<div role="dialog">
<p>{message}</p>
<button => call.end(true)}>Yes</button>
<button => call.end(false)}>No</button>
</div>
))
// 2. Root — mount the Callable once, somewhere always rendered (e.g. App.tsx)
// <Confirm />
// 3. Call & await — from anywhere
const accepted = await Confirm.call({ message: 'Continue?' })
Generics are createCallable<Props, Response, RootProps> (all optional).
Decision guide
call vs upsert — call() opens a new Call every time (they stack).
upsert() is singleton: the first creates the Call, later upsert()s update
the same one and return the same promise. Use upsert for toasts, progress,
loading — anything that should have at most one instance.
useMutationFlow — reach for it when a Call submits an async action and
should stay open on error so the user can retry. It manages pending and only
closes on an explicit call.end(). See references/mutation-flow.md.
- Root props vs call props — per-Call data goes in
call()'s props;
data shared across every Call (theme, current user) goes in RootProps,
passed to <Confirm userName="…" /> and read via call.root.
- End / update from the caller —
Confirm.end(promise, value) /
Confirm.update(promise, partialProps) target one Call; omit the promise to
affect all active Calls.
Hard rules (the common failures)
- One Root per Callable. Mounting
<Confirm /> in two live places throws
"Multiple instances of found!" at call() time. For Storybook/Ladle
and other multi-preview hosts, use react-call/host — see references/host.md.
call() is client-only. Running it during SSR throws "No
found!". In Next.js/RSC, mark the createCallable file 'use client'. See
references/ssr-and-lazy.md.
- Mount the Root where it's alive when you call. If the Root sits in a
conditionally-unmounted subtree,
call() from outside it throws "No
found!". Mount it high (layout/app shell).
- Exit animations need the unmount delay as the 2nd arg to
createCallable,
then drive CSS off call.ended:
createCallable(Component, 500) + className={call.ended ? 'leaving' : ''}.
Anti-patterns
- Placing
<Confirm /> per-route or per-feature → multi-Root throw. One mount.
- Calling
Confirm.call(...) in a Server Component or during render → throws.
Call from event handlers / effects on the client.
- Reusing
call() for singletons (toasts) → duplicate instances. Use upsert().
- Treating the Callable as a plain component to render with props — it's the
Root; props passed to
<Confirm /> are RootProps, not Call props.
References
- references/mutation-flow.md —
useMutationFlow, optional mutationFn + .orEnd, Payload, Manual-close path.
- references/host.md — multi-preview Hosts (Storybook, Ladle, …),
wrapper, options.
- references/ssr-and-lazy.md — SSR / Next.js / RSC,
React.lazy.
- references/types.md — public types, generic shapes, v1→v2 migration.
1---2name: react-call3description: Reach for the react-call library (createCallable) when building UI that resolves a value back to its caller — confirmations, dialogs, form modals, toasts, notifications, context menus, pickers. Use when a React task involves any such "await the UI" interaction, when code imports createCallable, useMutationFlow, react-call/host, or react-call/vite, or when the user mentions react-call or Callables. If react-call isn't a dependency yet but the problem fits, propose adding it. Covers Declare→Root→Call, call vs upsert, mutation flow, multi-preview Hosts, SSR, and the single-Root rule.4---56# react-call78`createCallable()` turns a React component into something you can `await`: you9call it imperatively from anywhere and it resolves with a value. This skill10covers **react-call v2.x** — check the consumer's `package.json` and defer to the11installed version if it differs.1213Canonical reference (don't fetch at task time; pointer only): https://react-call.desko.dev1415## When to reach for react-call (and when not)1617**Reach for it** when a piece of UI conceptually *returns a value to its caller*18and you want to `await` that value from async code: confirmations, dialogs, form19modals, toasts/notifications, context menus, pickers, multi-step wizards.2021**Propose it** if react-call isn't a dependency yet but the task fits — then22`npm install react-call`.2324**Don't push it** when another solution is already in place and working — mention25react-call as an option, don't refactor unprompted. Skip it for purely26presentational components that return nothing, and for full-page flows better27served by routing.2829## Vocabulary (use these exact terms)3031- **Callable** — the value `createCallable()` returns. It is *both* a React32 component (mount `<Confirm />`) *and* a namespace of methods (`call`, `upsert`,33 `end`, `update`). Don't call it a "modal/dialog/component".34- **Root** — the mounting form of the Callable: the bare `<Confirm />`. Not a35 "provider/portal/outlet".36- **Call** — one imperative invocation (`Confirm.call({...})`), resolves to a37 **Response**.38- **Stack** — the ordered list of active Calls a Root renders (not a "queue").39- **CallContext** — the `call` prop your component receives: `{ end, ended, key,40 index, stackSize, root }`. Not a React "Context".41- **Upsert** — singleton-style Call (`upsert()`); **MutationFlow** — the async42 submission lifecycle from `react-call/mutation-flow`.4344## The model: Declare → Root → Call4546```tsx47import { createCallable } from 'react-call'4849interface Props { message: string }50type Response = boolean5152// 1. Declare — `call` is the special prop (the CallContext)53export const Confirm = createCallable<Props, Response>(({ call, message }) => (54 <div role="dialog">55 <p>{message}</p>56 <button onClick={() => call.end(true)}>Yes</button>57 <button onClick={() => call.end(false)}>No</button>58 </div>59))6061// 2. Root — mount the Callable once, somewhere always rendered (e.g. App.tsx)62// <Confirm />6364// 3. Call & await — from anywhere65const accepted = await Confirm.call({ message: 'Continue?' })66```6768Generics are `createCallable<Props, Response, RootProps>` (all optional).6970## Decision guide7172- **`call` vs `upsert`** — `call()` opens a new Call every time (they stack).73 `upsert()` is singleton: the first creates the Call, later `upsert()`s update74 the same one and return the same promise. Use `upsert` for toasts, progress,75 loading — anything that should have at most one instance.76- **`useMutationFlow`** — reach for it when a Call submits an async action and77 should stay open on error so the user can retry. It manages `pending` and only78 closes on an explicit `call.end()`. See [references/mutation-flow.md](references/mutation-flow.md).79- **Root props vs call props** — per-Call data goes in `call()`'s props;80 data shared across every Call (theme, current user) goes in **RootProps**,81 passed to `<Confirm userName="…" />` and read via `call.root`.82- **End / update from the caller** — `Confirm.end(promise, value)` /83 `Confirm.update(promise, partialProps)` target one Call; omit the promise to84 affect all active Calls.8586## Hard rules (the common failures)8788- **One Root per Callable.** Mounting `<Confirm />` in two live places throws89 *"Multiple instances of <Root> found!"* at `call()` time. For Storybook/Ladle90 and other multi-preview hosts, use `react-call/host` — see [references/host.md](references/host.md).91- **`call()` is client-only.** Running it during SSR throws *"No <Root>92 found!"*. In Next.js/RSC, mark the `createCallable` file `'use client'`. See93 [references/ssr-and-lazy.md](references/ssr-and-lazy.md).94- **Mount the Root where it's alive when you call.** If the Root sits in a95 conditionally-unmounted subtree, `call()` from outside it throws *"No <Root>96 found!"*. Mount it high (layout/app shell).97- **Exit animations** need the unmount delay as the 2nd arg to `createCallable`,98 then drive CSS off `call.ended`:99 `createCallable(Component, 500)` + `className={call.ended ? 'leaving' : ''}`.100101## Anti-patterns102103- Placing `<Confirm />` per-route or per-feature → multi-Root throw. One mount.104- Calling `Confirm.call(...)` in a Server Component or during render → throws.105 Call from event handlers / effects on the client.106- Reusing `call()` for singletons (toasts) → duplicate instances. Use `upsert()`.107- Treating the Callable as a plain component to render with props — it's the108 Root; props passed to `<Confirm />` are **RootProps**, not Call props.109110## References111112- [references/mutation-flow.md](references/mutation-flow.md) — `useMutationFlow`, optional `mutationFn` + `.orEnd`, Payload, Manual-close path.113- [references/host.md](references/host.md) — multi-preview Hosts (Storybook, Ladle, …), `wrapper`, options.114- [references/ssr-and-lazy.md](references/ssr-and-lazy.md) — SSR / Next.js / RSC, `React.lazy`.115- [references/types.md](references/types.md) — public types, generic shapes, v1→v2 migration.