@karpo.dev/sdk-react
v0.4.0
Published
React bindings for the Karpo client — useQuery, useMutation, useSyncStatus.
Readme
@karpo.dev/sdk-react
React bindings for the Karpo client. A query re-runs after any write touching its tables, so there is no refetch to call and no cache to invalidate.
npm install @karpo.dev/sdk-reactPeer dependency: React ≥ 18. Pair it with @karpo.dev/sdk-web in a browser app.
Usage
Wire the client once, at the entry point. The provider is only a loading gate — remove it and the hooks still work.
import { KarpoProvider } from "@karpo.dev/sdk-react";
import { initKarpo, karpo } from "../karpo/generated/karpo.js";
void initKarpo({ user: "u01", target: "dev" });
createRoot(document.getElementById("root")!).render(
<KarpoProvider karpo={karpo} fallback={<p>Opening the local database…</p>}>
<App />
</KarpoProvider>,
);Then read and write from components:
import { useMutation, useQuery } from "@karpo.dev/sdk-react";
import { db } from "../karpo/generated/karpo.js";
export function Todos({ workspaceId }: { workspaceId: string }) {
const { data: todos = [], isLoading, error } = useQuery(
db.workspaces.todos.where({ containerId: workspaceId }).orderBy("title"),
);
// Lands in local SQLite first and syncs in the background: resolves offline.
const [addTodo, adding] = useMutation(async (title: string) =>
db.workspaces.todos.insert({ containerId: workspaceId, title }),
);
if (error) return <p role="alert">Could not read todos: {String(error)}</p>;
if (isLoading) return <p>Loading…</p>;
return (
<ul>
{todos.map((t) => <li key={t.id}>{t.title}</li>)}
<button disabled={adding.pending} onClick={() => void addTodo("New")}>Add</button>
</ul>
);
}Handling error is not optional: a failed query leaves isLoading false and
data undefined. Three more hooks take the same karpo handle —
useSyncStatus, useAuthSession and useCurrentUser.
For state that must survive a reload and must never reach the server — a draft,
a preference, a dismissed banner — usePersistentState is useState with a
device-local store behind it:
const [draft, setDraft] = usePersistentState("compose-draft", "");Once karpo.ready has resolved it returns the persisted value on its first
render, so there is no isLoading and no flash of the initial value over an
existing draft. The setter re-renders synchronously and persists in the
background. It shares one namespace with kv from the generated karpo.ts:
the same key names the same value. Schema tables synchronize; this does not.
Docs: https://karpo.dev/react. Source: https://github.com/Chirich-GmbH/karpo-core
