React hooks
Import hooks from covara/client/react. They build on the LiveQuery store and the typed repository.
useLiveList
The primary hook for real-time lists with optimistic mutations.
import { useLiveList } from "covara/client/react";
function TodoList() {
const {
items, status, statusLabel, error, pendingCount,
isLoading, isLive, isOffline, isReconnecting,
hasMore, totalCount, isLoadingMore,
mutate, refresh, loadMore,
} = useLiveList<Todo>("/api/todos", {
filter: 'userId=="123"',
orderBy: "position",
limit: 100,
include: "category,tags",
subscriptionMode: "strict",
enabled: true,
select: ["id", "title", "completed"],
});
return (
<ul>
{items.map((t) => (
<li key={t.id}>
<input type="checkbox" checked={t.completed}
onChange={() => mutate.update(t.id, { completed: !t.completed })} />
{t.title}
<button onClick={() => mutate.delete(t.id)}>×</button>
</li>
))}
</ul>
);
}
status: "loading" | "live" | "reconnecting" | "offline" | "error". mutate.create/update/delete apply optimistically. Pass a typed ResourceClient instead of a path to infer T:
const { items } = useLiveList(client.resources.todos, { orderBy: "position" });
Pagination
With a limit, the hook exposes hasMore, totalCount, isLoadingMore, and loadMore():
const { items, hasMore, loadMore, isLoadingMore } = useLiveList<Todo>("/api/todos", { limit: 20 });
{hasMore && <button onClick={loadMore} disabled={isLoadingMore}>Load more</button>}
Subscription modes
Control how other clients' changes affect a paginated view — strict (default with limit), sorted, append, prepend, or live (default without limit). See Subscriptions → modes.
Type-safe projections
const { items } = useLiveList<User, "id" | "name" | "avatar">("/api/users", { select: ["id", "name", "avatar"] });
// items: { id; name; avatar }[]
Relations & optimistic updates
With include, changing a foreign key clears the stale relation immediately and refills it from the server response. For instant UX, look it up from local cache (todo.category ?? categories.find(c => c.id === todo.categoryId)). See Subscriptions → relations.
useInfiniteList
Cursor-paginated live list; pages accumulate into items and stay realtime-aware.
const { items, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteList<Todo>("/api/todos", { limit: 20, orderBy: "createdAt:desc" });
useLiveAggregate
Live aggregation that recomputes on every change. See Aggregate subscriptions.
const { groups, isLive } = useLiveAggregate("/api/todos", { groupBy: ["completed"], count: true });
useMutation
Standalone mutation hook usable outside a list; integrates with optimistic updates, the offline queue, and invalidation.
const { mutate, mutateAsync, status, error, reset } = useMutation<Todo>("/api/todos", {
invalidates: ["/api/todos"],
onSuccess: (todo) => toast(`Created ${todo.id}`),
});
mutate({ kind: "create", data: { title: "New" } });
mutate({ kind: "update", id: "1", data: { completed: true } });
mutate({ kind: "delete", id: "2" });
// custom function form
const remove = useMutation(async ({ id }: { id: string }, ctx) => {
await ctx.resource.delete(id);
ctx.invalidate("/api/todos");
}, { resource: "/api/todos" });
useSearch
Debounced full-text search:
const { items, isSearching, search, clear } = useSearch(client.resources.todos, { enabled: true });
search("important");
Invalidation & prefetch
import { useInvalidate } from "covara/client/react";
const invalidate = useInvalidate();
await save();
invalidate("/api/todos"); // same semantics as client.invalidate
client.prefetch(path, options) warms the cache so a later useLiveList skips the loading flash. See Overview.
Other hooks
| Hook | Purpose | Page |
|---|---|---|
useAuth | Auth state (cookie/JWT/bearer/apiKey/auto) | Client auth |
useJWTAuth | JWT login/signup/refresh | JWT |
usePublicEnv | Public env vars | Environment variables |
useFileUpload / useFile / useFiles | File uploads | File uploads |
useCredits / useSubscription / useCheckout | Billing | Billing hooks |