SDK Hooks
React hooks for querying data, running mutations, and reading auth state.
useUser
Returns the current authenticated user, or null if signed out. isLoading is true during the initial session rehydration — use it to avoid flashing a logged-out UI on page reload.
import { useUser } from "@weirdscience/based-client";
function Profile() {
const { user, isLoading } = useUser();
if (isLoading) return <Spinner />;
if (!user) return <LoginForm />;
return <p>{user.email}</p>;
}| Field | Type | Description |
|---|---|---|
| user | { id, email } | null | Current user or null |
| isLoading | boolean | True during session hydration on mount |
| error | Error | null | Error if the request failed |
useQuery
Fetch records from a table with optional filtering and pagination. Pass your Tables interface and a table name as generics for full type inference (see based typegen).
import type { Tables } from "./based.d.ts";
import { useQuery } from "@weirdscience/based-client";
function PostList() {
// Untyped
const { data, total, isLoading, error, refetch } = useQuery("posts", {
filter: { status: "published" },
limit: 10,
offset: 0,
});
// Typed — data is Tables["posts"][], filter keys are type-checked
const { data: typedData } = useQuery<Tables, "posts">("posts", {
filter: { status: "published" },
limit: 10,
});
if (isLoading) return <p>Loading...</p>;
return (
<>
<p>{total} posts</p>
{data?.map(post => (
<div key={post.id}>{post.title}</div>
))}
<button onClick={refetch}>Refresh</button>
</>
);
}Options
| Option | Type | Description |
|---|---|---|
| filter | Record<string, any> | Column filters as key-value pairs |
| limit | number | Max records to return |
| offset | number | Number of records to skip |
| enabled | boolean | When false, skip the request. Defaults to true. |
Gate queries behind auth or any boolean to avoid unnecessary 403s:
const { user } = useUser();
const { data } = useQuery("notes", { enabled: !!user });
// Won't fire until user is signed in.Return value
| Field | Type | Description |
|---|---|---|
| data | Row[] (typed) or any[] | Array of records |
| total | number | Total matching records (before pagination) |
| isLoading | boolean | True while fetching |
| error | Error | null | Error if the request failed |
| refetch | () => void | Re-run the query |
useRecord
Fetch a single record by id via GET /api/:table/:id. Unlike useQuery, data is a single row or null — not an array.
import type { Tables } from "./based.d.ts";
import { useRecord } from "@weirdscience/based-client";
function Post({ id }: { id: string }) {
const { data: post, isLoading } = useRecord<Tables, "posts">("posts", id);
if (isLoading) return <Spinner />;
if (!post) return <NotFound />;
return <h1>{post.title}</h1>;
}Pass null or undefined as the id to skip the query — no need for a separate enabled flag:
const [selectedId, setSelectedId] = useState<string | null>(null);
const { data } = useRecord("posts", selectedId);
// No fetch until setSelectedId is called.Perfect for deterministic keys like ${userId}:${key} paired with PUT upsert:
const { data: prefs } = useRecord("preferences", `${user.id}:theme`);Return value
| Field | Type | Description |
|---|---|---|
| data | Row (typed) or null | The record, or null if not found |
| isLoading | boolean | True while fetching |
| error | Error | null | Error if the request failed |
| refetch | () => void | Re-run the query |
useMutation
Create, update, or delete records. The operation parameter determines the behavior. Pass <Tables, "tableName"> for typed mutation payloads.
import type { Tables } from "./based.d.ts";
import { useMutation } from "@weirdscience/based-client";
function CreatePost() {
// Typed — mutate() payload is type-checked against Tables["posts"]
const { mutate, isLoading, error } = useMutation<Tables, "posts">("posts", "create");
return (
<button
disabled={isLoading}
onClick={() => mutate({ title: "New Post", status: "draft" })}
>
Create
</button>
);
}
function UpdatePost({ id }: { id: number }) {
const { mutate } = useMutation<Tables, "posts">("posts", "update");
return (
<button onClick={() => mutate({ id, status: "published" })}>
Publish
</button>
);
}
function DeletePost({ id }: { id: number }) {
const { mutate } = useMutation<Tables, "posts">("posts", "delete");
return (
<button onClick={() => mutate({ id })}>
Delete
</button>
);
}Operations
| Operation | mutate(data) | Notes |
|---|---|---|
| create | { ...fields } | All required columns must be provided |
| update | { id, ...fields } | id is required, only changed fields needed |
| delete | { id } | id is required |
Return value
| Field | Type | Description |
|---|---|---|
| mutate | (data: object) => Promise<void> | Execute the mutation |
| isLoading | boolean | True while the mutation is in flight |
| error | Error | null | Error if the mutation failed |