@kizaki/react
React hooks for live queries, mutations, presence, and broadcast.
@kizaki/react wraps BrowserKizakiClient in hooks that handle subscriptions, loading states, cache invalidation, and cleanup.
KizakiProvider
Optional context provider that supplies a custom BrowserKizakiClient to all hooks in the tree. If omitted, hooks create and share a default client.
import { KizakiProvider } from "@kizaki/react";
import { createBrowserClient } from "@kizaki/sdk/browser";
const client = createBrowserClient();
function App() {
return (
<KizakiProvider client={client}>
<Dashboard />
</KizakiProvider>
);
}Useful when you want explicit client configuration or a mock client for testing.
useQuery
useQuery(builder, options?) subscribes to a query and returns its current state. Live by default — data updates automatically via WebSocket.
import { useQuery } from "@kizaki/react";
import { select } from "@kizaki/sdk/browser";
import { Project } from "@kizaki/schema";
const projectsQuery = select(Project)
.fields(Project.id, Project.name)
.orderBy(Project.createdAt, "desc");
function ProjectList() {
const { data, totalCount, status, error, refetch } = useQuery(projectsQuery);
if (status === "loading") return <p>Loading projects...</p>;
if (status === "error") return <p>{error.message}</p>;
return (
<ul>
{data.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
);
}Return Value
| Field | Type | Description |
|---|---|---|
data | Row[] | undefined | Query results, typed to match selected fields |
totalCount | number | undefined | Total matching rows (before limit/offset) |
status | "loading" | "success" | "error" | Current query state |
error | Error | undefined | Error object if the query failed |
refetch | () => void | Manually trigger a refetch |
Options
| Option | Type | Default | Description |
|---|---|---|---|
live | boolean | true | Subscribe to live updates. Set false for a one-shot fetch |
// One-shot fetch — no live updates
const { data, status } = useQuery(projectsQuery, { live: false });useMutation
useMutation(fn, options?) wraps an async function (typically from @kizaki/client) with loading state, error handling, and cache invalidation.
import { useMutation, useQuery } from "@kizaki/react";
import { select } from "@kizaki/sdk/browser";
import { createProject } from "@kizaki/client";
import { Project } from "@kizaki/schema";
const projectsQuery = select(Project).orderBy(Project.createdAt, "desc");
function CreateProjectButton() {
const create = useMutation(createProject, {
invalidate: [projectsQuery],
});
return (
<>
<button onClick={() => create.mutate("New Project")} disabled={create.pending}>
New Project
</button>
{create.error && <p>{create.error.toUserMessage()}</p>}
</>
);
}Return Value
| Field | Type | Description |
|---|---|---|
mutate | (...args) => Promise | Call the wrapped function with its original arguments |
pending | boolean | True while the mutation is in flight |
error | Error | undefined | Error from the most recent failed call |
Options
| Option | Type | Description |
|---|---|---|
invalidate | Builder[] | "all" | Query builders to refetch after success. "all" invalidates every active query |
optimistic | (client, ...args) => rollback | Optimistic update function (see below) |
onSuccess | (result) => void | Callback after successful mutation |
onError | (error) => void | Callback after failed mutation |
useSubscription
useSubscription(builder) is always-live. Returns an explicit unsubscribe function for manual lifecycle control.
import { useSubscription } from "@kizaki/react";
import { select } from "@kizaki/sdk/browser";
import { Message } from "@kizaki/schema";
function ChatFeed({ channelId }: { channelId: string }) {
const { data, status, unsubscribe } = useSubscription(
select(Message)
.where(eq(Message.channelId, channelId))
.orderBy(Message.createdAt, "desc")
.limit(100)
);
if (status === "loading") return <p>Connecting...</p>;
return (
<ul>
{data.map((msg) => (
<li key={msg.id}>{msg.text}</li>
))}
</ul>
);
}For most cases, useQuery with the default live: true is sufficient. Use useSubscription when you need explicit unsubscribe control.
usePresence
usePresence(channel, id?) tracks connected user counts. Updates automatically as users join and leave.
import { usePresence } from "@kizaki/react";
function ViewerCount({ docId }: { docId: string }) {
const { count } = usePresence("document", docId);
return <span>{count} {count === 1 ? "person" : "people"} viewing</span>;
}useBroadcast
useBroadcast(channel, id?) provides ephemeral messaging for real-time collaboration. Messages are not persisted.
import { useBroadcast } from "@kizaki/react";
function CollaborativeCursor({ docId }: { docId: string }) {
const { send, lastMessage } = useBroadcast("document", docId);
const handleMouseMove = (e: React.MouseEvent) => {
send({ type: "cursor", x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove}>
{lastMessage?.type === "cursor" && (
<div
style={{ position: "absolute", left: lastMessage.x, top: lastMessage.y }}
className="remote-cursor"
/>
)}
</div>
);
}lastMessage is undefined until the first message arrives.
Optimistic Updates
The optimistic option on useMutation updates the UI before server confirmation. If the mutation fails, the rollback function restores previous state.
import { useMutation, useQuery } from "@kizaki/react";
import { select } from "@kizaki/sdk/browser";
import { createProject } from "@kizaki/client";
import { Project } from "@kizaki/schema";
const projectsQuery = select(Project).orderBy(Project.createdAt, "desc");
function CreateProjectButton() {
const create = useMutation(createProject, {
invalidate: [projectsQuery],
optimistic: (client, name) => {
const rollback = client.updateQuery(projectsQuery, (current) => ({
...current,
rows: [
{ id: "temp", name, createdAt: new Date() },
...current.rows,
],
}));
return rollback;
},
});
return (
<button onClick={() => create.mutate("New Project")} disabled={create.pending}>
New Project
</button>
);
}The optimistic function receives the BrowserKizakiClient and the mutation arguments. Call client.updateQuery() to patch cached state and return the rollback function. On success, the invalidate list triggers a real refetch. On failure, the rollback restores the previous cache state.
Error Handling
Both useQuery and useMutation expose errors through their return values.
const { data, error, status } = useQuery(projectsQuery);
if (status === "error") {
return <p>{error.message}</p>;
}For mutations:
const create = useMutation(createProject, { invalidate: [projectsQuery] });
// In your JSX:
{create.error && <p>{create.error.toUserMessage()}</p>}SDK errors carry typed code fields for programmatic handling. Use .toUserMessage() for display text.
Auth Hooks
Authentication hooks are available from @kizaki/sdk/auth/react:
useAuth()— manages auth state, exposesuser,loading,login,signup,logout, and OAuth helpersAuthGate— renders children when authenticated, a fallback when not
See the Auth reference.
Related guide: Realtime