React and offline UX
ramose/react is a thin adapter over the framework-neutral client. It adds React subscriptions, not another cache, query language, or mutation state machine.
Observe a query
Section titled “Observe a query”const tasks = useQuery(projectDb.query.from(Task).orderBy(Task.createdAt))QueryState<A> is explicit:
type QueryState<A> = | { status: "pending" } | { status: "ready"; data: A } | { status: "stale"; data: A } | { status: "error"; error: unknown }pending means there is no complete local answer yet. stale means a complete cached answer is available while resume is pending or the network is offline. Keep stale data visible and label connectivity; do not replace it with a full-screen spinner.
Render entity handles
Section titled “Render entity handles”if (tasks.status === "pending") return <TaskListSkeleton />if (tasks.status === "error") return <ErrorPanel error={tasks.error} />
return tasks.data.map((task) => ( <TaskRow key={task.data.id} task={task.data} pending={task.local.pending} onDone={(done) => task.mutate.setDone({ done })} />))Plain .data remains cloneable and free of methods or client metadata. A component rerenders when the entity data or its .local state changes.
Track one mutation
Section titled “Track one mutation”const [receipt, setReceipt] = useState<Receipt | null>(null)const state = useReceipt(receipt)
function save(title: string) { setReceipt(task.mutate.rename({ title }))}Use entity pending state for lightweight row feedback and useReceipt when the UI needs exact queued, committed, rejected, output, or retry information. Disable destructive repeat actions while one receipt is in flight, but do not disable unrelated offline work.
Show synchronization separately
Section titled “Show synchronization separately”const sync = useSyncState(client)Connectivity is not query validity. A ready or stale query can be useful while offline. A connected client may still have a rejected receipt. Keep the two signals distinct in UI copy.
React guarantees
Section titled “React guarantees”Hooks use React’s external-store contract, so snapshots are stable under concurrent rendering. Strict Mode does not duplicate activation, subscriptions, or operations. Unmounting a component removes its observer; the database in the session remains synchronized.
Server Components may render ordinary server-side queries, but browser database handles and offline replicas belong in Client Components. There is no persisted SSR query-result cache.