react-call v2

Why react-call?

Short version: for showing and hiding something, React state is perfect. The moment the interaction has to answer you, that model starts to strain — and what you build to compensate is, more or less, this library.

An imperative flow in a declarative model

A confirm, a prompt, a picker is imperative by nature: ask a question, wait, get an answer, continue. React state is declarative — it describes what the UI is for a given state, not a step-by-step flow that returns a value. Forcing the imperative interaction through declarative state is the mismatch. Everything below is a symptom of it.

react-call doesn't make React less declarative — the Root still renders its active calls declaratively. It gives the imperative half an imperative API, await Confirm.call(), and leaves rendering where it belongs. Each half does what it's good at.

State renders the dialog. It can't return the answer.

You can keep a dialog in React state easily enough — a flag to show it, even the choice once it's made. What state can't do is hand that choice back to the imperative code that opened it. So you bridge the gap by hand: a callback prop, or a resolver kept in state, lifted to a common parent — and the consequence of the decision ends up stranded in that handler, far from the click that triggered it.

By hand
function DeleteButton({ id }) {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Delete</button>
{open && (
<Confirm
// the consequence — stranded here, far
// from the button that started it
onAccept={() => { api.delete(id); setOpen(false) }}
onCancel={() => setOpen(false)}
/>
)}
</>
)
}
With react-call
function DeleteButton({ id }) {
return (
<button
onClick={async () => {
// ask, await, act — one place
if (await Confirm.call()) {
await api.delete(id)
}
}}
>
Delete
</button>
)
}

“But why add a dependency?”

Fair. You can hand-roll a promise so you can await the answer and keep the flow in one place. But that wrapper — once it also handles typing, SSR, the one-Root-per-component rule, exit animations, and more than one call on screen at once — is react-call. It's ~1 KB, has no dependencies, and it's already written and tested.

When React state is the right call

None of this is "React state is bad". For a pure show/hide toggle that returns nothing — a menu that just opens and closes — a useState is exactly right, and you shouldn't reach for a library. The line is the result: the moment you need an answer back, several of these on screen at once, or the same dialog reused from many places, a call() earns its keep.

  • Composition — two questions at once are just two calls living together, not a set of open/close flags you wire up by hand. See the Stack →
  • Reuse — define the Callable once, call it from any handler, hook or domain action. No re-wiring per screen.