Skip to content

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.

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.

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:

OptionDescription
selectorThe selector to run
argsIts arguments (also the cache key)
defaultValueValue returned before the first result / when disabled
enabledSet false to skip running; returns defaultValue
gcTimeOverride the cache garbage-collection time

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: [],
});

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.

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" }));
};
HookReturnsFor
useDB()the SubscribableDBaccessing the DB directly
useSyncSelector(opts)the selector resultreactive read, sync drivers
useAsyncSelector(opts)the result or defaultreactive read, async drivers
useDispatch()(action) => TReturnwrite, sync drivers
useAsyncDispatch()(action) => Promise<TReturn>write, async drivers
useSelect()(gen) => TReturnone-off read, sync drivers
useAsyncSelect()(gen) => Promise<TReturn>one-off read, async drivers