react-call v2

Account-aware dialog

A dialog that greets the signed-in user without the caller ever passing their name. The user lives on a Root prop and reaches every call through call.root — separate from the per-call props.

Root props
→ awaiting click…

The Callable

declared once, mounted in the React tree
Greeter.tsx
import { createCallable } from 'react-call'
interface Props {
message: string
}
type Response = boolean
// The third generic is RootProps — what the Root mount accepts and what
// each call reads via `call.root`. It's separate from the per-call Props.
type RootProps = {
userName: string
}
export const Greeter = createCallable<Props, Response, RootProps>(
({ call, message }) => (
<div
role="dialog"
aria-modal="true"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
>
<div className="w-full max-w-sm rounded-lg border border-[var(--color-border)] bg-[var(--color-bg)] p-6 shadow-2xl">
<p className="font-mono text-xs uppercase tracking-wider text-[var(--color-fg-subtle)]">
Signed in as {call.root.userName}
</p>
<p className="mt-2 text-base text-[var(--color-fg)]">{message}</p>
<div className="mt-6 flex items-center justify-end gap-3">
<button
type="button"
onClick={() => call.end(false)}
className="rounded-md border border-[var(--color-border)] px-4 py-2 text-sm font-medium text-[var(--color-fg-muted)] transition-colors hover:text-[var(--color-fg)]"
>
Not now
</button>
<button
type="button"
onClick={() => call.end(true)}
className="rounded-md bg-[var(--color-accent)] px-4 py-2 text-sm font-medium text-[var(--color-accent-fg)] transition-colors hover:bg-[var(--color-accent-hover)]"
>
Enable
</button>
</div>
</div>
</div>
),
)
Greeter.displayName = 'Greeter'

The Root

mounted in your app tree, once
App.tsx
import { Greeter } from './Greeter'
import { AskButton } from './AskButton'
export default function App() {
return (
<>
<Greeter userName="Ada Lovelace" />
<AskButton />
</>
)
}

The caller

anywhere in your app, imperative
AskButton.tsx
import { useState } from 'react'
import { Greeter } from './Greeter'
export const AskButton = () => {
const [status, setStatus] = useState<'idle' | 'enabled' | 'dismissed'>('idle')
const handleClick = async () => {
// Note what the caller does NOT pass: the user's name. That lives on
// the Root and reaches the call through `call.root` — the caller only
// supplies the per-call message.
const enabled = await Greeter.call({
message: 'Enable two-factor authentication for your account?',
})
setStatus(enabled ? 'enabled' : 'dismissed')
}
return (
<div className="flex flex-col items-center gap-3">
<button
type="button"
onClick={handleClick}
className="rounded-md bg-[var(--color-accent)] px-4 py-2 text-sm font-medium text-[var(--color-accent-fg)] transition-colors hover:bg-[var(--color-accent-hover)]"
>
Review security
</button>
<span className="font-mono text-xs text-[var(--color-fg-subtle)]">
{status === 'idle' && '→ awaiting click…'}
{status === 'enabled' && (
<span className="text-[var(--color-accent)]">→ 2FA enabled</span>
)}
{status === 'dismissed' && '→ dismissed'}
</span>
</div>
)
}

Two kinds of props

A call has per-call props (passed at Greeter.call({ message })) and Root props (passed at the mount, <Greeter userName="Ada Lovelace" />). The third generic on createCallable<Props, Response, RootProps> types the latter, and every call reads them through call.root:

<p>Signed in as {call.root.userName}</p>

The caller never forwards the user’s name — it would be noise at every callsite. The Root holds it once; each call projects it.

When to reach for Root props

  • Share one value with every call — the current user, tenant, locale, feature flags. Set it on the Root, read it from call.root.
  • Surface something only the Root’s parent has — a theme object, a router, anything in scope where you mount <Greeter /> but not where you call it.
  • Push live data into open calls. Root props are reactive: when the Root re-renders with a new userName, every active call re-renders with it too — no update() needed, because it isn’t a per-call prop.

Per-call or Root prop?

If the value changes per invocation (the message, the item being acted on), it’s a per-call prop. If it’s ambient — the same for every call and owned by the surrounding app — it’s a Root prop.

Related examples