LLMSkillHub
mirroredCopied from zosmaai/zosma-cowork under MIT. zosmaai has not claimed this page, and is not affiliated with LLM SkillHub.

zosmaai / react-call

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.

0
SKILL.md
5.9 KB

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 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

  • 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](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 <Root> found!" at call() time. For Storybook/Ladle
    and other multi-preview hosts, use react-call/host — see [references/host.md](references/host.md).

  • call() is client-only. Running it during SSR throws *"No <Root>
    found!"*. In Next.js/RSC, mark the createCallable file 'use client'. See
    [references/ssr-and-lazy.md](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 <Root>
    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](references/mutation-flow.md) — useMutationFlow, optional mutationFn + .orEnd, Payload, Manual-close path.
  • [references/host.md](references/host.md) — multi-preview Hosts (Storybook, Ladle, …), wrapper, options.
  • [references/ssr-and-lazy.md](references/ssr-and-lazy.md) — SSR / Next.js / RSC, React.lazy.
  • [references/types.md](references/types.md) — public types, generic shapes, v1→v2 migration.