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.
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)} /> )} </> )}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.