About react-call
A skill guide for react-call v2.x, which makes React components callable from async code and lets them resolve with a value. It helps React developers choose and apply the pattern for dialogs, confirmations, pickers, and other UI that returns a result.
Key features
- 01Imperative calls that resolve with values
- 02Call stack and singleton upsert patterns
- 03Root and per-call prop guidance
- 04Async mutation flow guidance
- 05SSR and mounting constraints
Use cases
- 01Awaiting a confirmation result
- 02Opening a picker or form modal from async code
- 03Showing a singleton toast or progress UI
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ desko27/ react-call --skill react-callAfter installing, type/react-callin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/host.mdmutation-flow.mdssr-and-lazy.mdtypes.mdDetails
From the project READMEreact-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
callprop your component receives:{ end, ended, key, index, stackSize, root }. Not a React "Context". - Upsert — singleton-style Call (
upsert()); MutationFlow — the async submission lifecycle fromreact-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 onClick={() => call.end(true)}>Yes</button>
<button onClick={() => 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
callvsupsert—call()opens a new Call every time (they stack).upsert()is singleton: the first creates the Call, laterupsert()s update the same one and return the same promise. Useupsertfor 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 managespendingand only closes on an explicitcall.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 viacall.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!" atcall()time. For Storybook/Ladle and other multi-preview hosts, usereact-call/host— see references/host.md. call()is client-only. Running it during SSR throws "No found!". In Next.js/RSC, mark thecreateCallablefile'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 offcall.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. Useupsert(). - 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, optionalmutationFn+.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.