# Client

The browser side of a fold: the typed client, query and mutation hooks, live updates, pagination, errors, and routing.

`client/index.tsx` must export a Preact component named `App`. Client code may import from `@aozumi/fold/client`, `preact`, `preact/hooks`, `preact/jsx-runtime`, `preact/jsx-dev-runtime`, and relative files. Style with complete Tailwind class names in JSX. Fold compiles the CSS from your client files.

## createClient

```tsx
import { createClient } from "@aozumi/fold/client";
import type app from "../server/index";

const client = createClient<typeof app>();
```

Use a type-only import of the server. The client then knows every query, mutation, and action name, its arguments, and its result type. Results are typed as the JSON your server returns.

`createClient(options?)` takes `{ optimistic?: boolean }`. See [Optimistic updates](#optimistic-updates).

The returned client has:

| Member | Signature |
|---|---|
| `useQuery` | `(name, ...args) => Result \| undefined` |
| `useMutation` | `(name) => (...args) => Promise<Result>` |
| `useAction` | `(name) => (...args) => Promise<Result>` |
| `usePaginatedQuery` | `(name, args, options?: { initialNumItems?: number }) => PaginatedQueryResult<Item>` |
| `storage` | Upload API. Local dev only for now. See [Storage](/fold/storage/). |

## Queries

```tsx
function TaskList() {
  const tasks = client.useQuery("myTasks");
  if (tasks === undefined) {
    return <p>Loading</p>;
  }
  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  );
}
```

- Returns `undefined` until the first result arrives. A real `null` result is returned as `null`.
- Pass the query's argument after the name: `client.useQuery("tasksForList", { listId })`.
- If the query throws on the server, `useQuery` throws the error during render. Wrap the component in `ErrorBoundary`.

## Live updates

Queries are live. Each page keeps one WebSocket to the fold. When a mutation, a writable endpoint, or a guest upgrade changes a table, every subscribed query that read that table runs again and the new result reaches the page. You do not refetch by hand.

After a redeploy, open pages are told to pick up the new version.

## Mutations

```tsx
function AddTask() {
  const addTask = client.useMutation("addTask");
  async function onSubmit(event: SubmitEvent) {
    event.preventDefault();
    const form = event.currentTarget as HTMLFormElement;
    const title = String(new FormData(form).get("title") ?? "").trim();
    if (!title) {
      return;
    }
    await addTask(title);
    form.reset();
  }
  return (
    <form onSubmit={(event) => void onSubmit(event)}>
      <input name="title" className="border px-2 py-1" />
      <button type="submit">Add</button>
    </form>
  );
}
```

- The promise resolves with the mutation's return value after the caller's queries show the write. It rejects with the server's error message.
- A page sends at most 4 mutations or actions at once. Later calls wait their turn and are sent in call order. If one write depends on another, `await` the first.

## Actions

```tsx
const noteCount = client.useAction("noteCount");
const { total } = await noteCount();
```

Actions are one-shot calls. They are not live and cannot write to the database.

## Paginated queries

Server:

```ts
feed: query(async (ctx, args: { pagination: PaginationOptions }) =>
  ctx.db.posts.withIndex("by_creation").order("desc").paginate(args.pagination)
)
```

Client:

```tsx
function Feed() {
  const feed = client.usePaginatedQuery("feed", {}, { initialNumItems: 20 });
  return (
    <div>
      {feed.page.map((post) => (
        <p key={post.id}>{post.body}</p>
      ))}
      {feed.isDone ? null : <button type="button" onClick={() => feed.loadMore()}>Load more</button>}
    </div>
  );
}
```

The query must take one object argument with a `pagination` field and return the result of `paginate()`. Pass the other fields as `args`; Fold adds `pagination`. The result has `page` (all loaded items), `isDone`, `continueCursor`, `loadMore()`, and `reset()`. `initialNumItems` defaults to 20. Every loaded page stays live.

## Errors

```tsx
import { ErrorBoundary } from "@aozumi/fold/client";

<ErrorBoundary fallback={(error, retry) => (
  <div>
    <p role="alert">{error.message}</p>
    <button type="button" onClick={retry}>Try again</button>
  </div>
)}>
  <TaskList />
</ErrorBoundary>
```

## Optimistic updates

`createClient({ optimistic: false })` turns optimistic updates off for the page. When a host supports them, Fold can show a mutation's writes before the server confirms them, with no app code. Today neither `npx @aozumi/fold dev` nor hosted folds offer them, so every mutation updates the page when the server answers. Write your UI so it does not depend on instant updates.

## Routing

```tsx
import { Link, Route, Router, Routes, useNavigate, useParams } from "@aozumi/fold/client";

function ListPage() {
  const { listId } = useParams<{ listId: string }>();
  const navigate = useNavigate();
  return (
    <section>
      <h1>List {listId}</h1>
      <button type="button" onClick={() => navigate("/")}>Back</button>
    </section>
  );
}

export function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/lists/:listId" element={<ListPage />} />
        <Route path="*" element={<p>Not found</p>} />
      </Routes>
    </Router>
  );
}
```

| Export | Use |
|---|---|
| `Router` | Wraps the app. |
| `Routes`, `Route` | Renders the first `Route` whose `path` matches. `:name` matches one segment; `*` matches the rest. |
| `Link` | `<a>` that navigates without a reload. Props: `to`, `replace?`, plus any anchor props. |
| `navigate(to, { replace? })`, `useNavigate()` | Navigate from code. |
| `useLocation()` | `{ pathname, search, hash, href }` |
| `useParams()` | Values for `:name` segments, and `*`. |

There is no file-based routing. Deep links work: a browser request for any page path that is not an endpoint gets the app.

## Auth hooks

`useAuth`, `SignInWithGoogle`, `signInWithGoogle`, `signOut`, `getIdentity`, `retryAuth`, and `canAccessApp` are covered in [Auth](/fold/auth/).

## Standalone hooks

`useQuery`, `useMutation`, `useAction`, and `usePaginatedQuery` are also exported directly from `@aozumi/fold/client`, untyped: `useQuery<Task[]>("myTasks")`. Prefer the typed `createClient` versions.
