This page is mirrored from packages/lix/react-utils.
React 19 hooks and helpers for building reactive UIs on top of the Lix SDK. These utilities wire Kysely queries to React Suspense and subscribe to live database updates.
LixProvider, useLix, useQuery, useQueryTakeFirst, useQueryTakeFirstOrThrownpm i @lix-js/react-utilsuse() and Suspense)Wrap your app with LixProvider and pass a Lix instance.
import { createRoot } from "react-dom/client";
import { LixProvider } from "@lix-js/react-utils";
import { openLix } from "@lix-js/sdk";
async function bootstrap() {
const lix = await openLix({});
const root = createRoot(document.getElementById("root")!);
root.render(
<LixProvider lix={lix}>
<App />
</LixProvider>,
);
}
bootstrap();Subscribe to a live query using React Suspense. The callback receives an object with { lix } and must return a Kysely SelectQueryBuilder.
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { useQuery } from "@lix-js/react-utils";
function KeyValueList() {
const rows = useQuery(({ lix }) =>
lix.db.selectFrom("key_value").where("key", "like", "demo_%").selectAll(),
);
return (
<ul>
{rows.map((r) => (
<li key={r.key}>
{r.key}: {r.value}
</li>
))}
</ul>
);
}
export function Page() {
return (
<Suspense fallback={<div>Loading…</div>}>
<ErrorBoundary fallbackRender={() => <div>Failed to load.</div>}>
<KeyValueList />
</ErrorBoundary>
</Suspense>
);
}Options
// One-time execution (no live updates)
const rows = useQuery(({ lix }) => lix.db.selectFrom("config").selectAll(), {
subscribe: false,
});subscribe !== false and updates state on emissions.When you want just one row:
import {
useQueryTakeFirst,
useQueryTakeFirstOrThrow,
} from "@lix-js/react-utils";
// First row or undefined
const file = useQueryTakeFirst(({ lix }) =>
lix.db.selectFrom("file").select(["id", "path"]).where("id", "=", fileId),
);
// First row or throw (suspends, then throws to ErrorBoundary if not found)
const activeVersion = useQueryTakeFirstOrThrow(({ lix }) =>
lix.db
.selectFrom("active_version")
.innerJoin("version", "version.id", "active_version.version_id")
.selectAll("version"),
);This package aligns with the SDK’s object-arg convention:
import { selectWorkingDiff } from "@lix-js/sdk";
const rows = useQuery(({ lix }) =>
selectWorkingDiff({ lix })
.where("diff.status", "!=", "unchanged")
.orderBy("diff.entity_id"),
);When building experiences like rich text editors, dashboards, or collaborative views, you often need to synchronize external changes while avoiding feedback loops from your own writes. Lix provides a simple pattern for this using a “writer key” and commit events.
See the guide for the pattern, pitfalls, and a decision matrix:
import { LixProvider, useLix } from "@lix-js/react-utils";
function NeedsLix() {
const lix = useLix(); // same instance passed to LixProvider
// …
}Why does the callback receive { lix } and not just lix?
Can I do imperative fetching?
lix.db directly in event handlers. useQuery is for declarative, Suspense-friendly reads.useQuery<TRow>(...) infers the row shape from your Kysely selection. You can also provide an explicit generic to guide inference if needed.Apache-2.0