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.

example.tsx
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>;
}
FieldTypeDescription
user{ id, email } | nullCurrent user or null
isLoadingbooleanTrue during session hydration on mount
errorError | nullError 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).

example.tsx
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

OptionTypeDescription
filterRecord<string, any>Column filters as key-value pairs
limitnumberMax records to return
offsetnumberNumber of records to skip
enabledbooleanWhen false, skip the request. Defaults to true.

Gate queries behind auth or any boolean to avoid unnecessary 403s:

example.tsx
const { user } = useUser();
const { data } = useQuery("notes", { enabled: !!user });
// Won't fire until user is signed in.

Return value

FieldTypeDescription
dataRow[] (typed) or any[]Array of records
totalnumberTotal matching records (before pagination)
isLoadingbooleanTrue while fetching
errorError | nullError if the request failed
refetch() => voidRe-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.

example.tsx
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:

example.tsx
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:

example.tsx
const { data: prefs } = useRecord("preferences", `${user.id}:theme`);

Return value

FieldTypeDescription
dataRow (typed) or nullThe record, or null if not found
isLoadingbooleanTrue while fetching
errorError | nullError if the request failed
refetch() => voidRe-run the query

useMutation

Create, update, or delete records. The operation parameter determines the behavior. Pass <Tables, "tableName"> for typed mutation payloads.

example.tsx
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

Operationmutate(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

FieldTypeDescription
mutate(data: object) => Promise<void>Execute the mutation
isLoadingbooleanTrue while the mutation is in flight
errorError | nullError if the mutation failed