React
The React integration lives at @will-be-done/hyperdb/react. It provides a
context provider plus hooks for reactive reads, dispatching actions, and one-off
reads. React 19 is a peer dependency.
Unlike MobX, HyperDB hands your components plain, immutable rows — frozen
data, never proxies — so there is no observer() wrapper to remember and
nothing leaking into your view layer. The hooks subscribe through HyperDB’s
range-tracked selector cache, so a component
re-renders only when a mutation touches a range its selector actually scanned —
fine-grained updates that compose with React’s rendering model directly.
Providing the database
Section titled “Providing the database”Wrap your tree in DBProvider and pass a SubscribableDB as its
value. Every hook reads the database from this context.
import { DBProvider } from "@will-be-done/hyperdb/react";import { db } from "./db";
export function App() { return ( <DBProvider value={db}> <Tasks projectId="p1" /> </DBProvider> );}useDB() returns the database from context (and throws if there’s no provider) —
useful for passing the DB to non-hook utilities.
Reactive reads
Section titled “Reactive reads”useSyncSelector
Section titled “useSyncSelector”For synchronous drivers (in-memory, sync SQLite). It subscribes to a cached selector and re-renders only when the selector’s scanned ranges change.
import { useSyncSelector } from "@will-be-done/hyperdb/react";import { projectTasks } from "./tasks";
function Tasks({ projectId }: { projectId: string }) { const tasks = useSyncSelector({ selector: projectTasks, args: { projectId }, defaultValue: [], }); return ( <ul> {tasks.map((t) => ( <li key={t.id}>{t.title}</li> ))} </ul> );}Options:
| Option | Description |
|---|---|
selector | The selector to run |
args | Its arguments (also the cache key) |
defaultValue | Value returned before the first result / when disabled |
enabled | Set false to skip running; returns defaultValue |
gcTime | Override the cache garbage-collection time |
useAsyncSelector
Section titled “useAsyncSelector”For asynchronous drivers (IndexedDB, async SQLite). Same shape, but the result
arrives asynchronously, so it returns defaultValue (or undefined) until the
first run resolves, and re-runs on relevant changes.
const tasks = useAsyncSelector({ selector: projectTasks, args: { projectId }, defaultValue: [],});Writing
Section titled “Writing”useDispatch / useAsyncDispatch
Section titled “useDispatch / useAsyncDispatch”Return a function that dispatches an action against the context database.
import { useDispatch } from "@will-be-done/hyperdb/react";import { createTask } from "./tasks";
function AddButton({ projectId }: { projectId: string }) { const dispatch = useDispatch(); return ( <button onClick={() => dispatch( createTask({ id: crypto.randomUUID(), projectId, title: "New" }), ) } > Add </button> );}useAsyncDispatch returns a function that yields a Promise — use it with async
drivers.
One-off reads
Section titled “One-off reads”useSelect and useAsyncSelect return a function for imperative, non-reactive
reads — for example reading inside an event handler. They don’t subscribe.
import { useSelect } from "@will-be-done/hyperdb/react";
const select = useSelect();const handleClick = () => { const tasks = select(projectTasks({ projectId: "p1" }));};Hook reference
Section titled “Hook reference”| Hook | Returns | For |
|---|---|---|
useDB() | the SubscribableDB | accessing the DB directly |
useSyncSelector(opts) | the selector result | reactive read, sync drivers |
useAsyncSelector(opts) | the result or default | reactive read, async drivers |
useDispatch() | (action) => TReturn | write, sync drivers |
useAsyncDispatch() | (action) => Promise<TReturn> | write, async drivers |
useSelect() | (gen) => TReturn | one-off read, sync drivers |
useAsyncSelect() | (gen) => Promise<TReturn> | one-off read, async drivers |