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.
The Callable
declared once, mounted in the React treeimport { 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, onceimport { Greeter } from './Greeter'import { AskButton } from './AskButton'
export default function App() { return ( <> <Greeter userName="Ada Lovelace" /> <AskButton /> </> )}The caller
anywhere in your app, imperativeimport { 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 — noupdate()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.