> ## Documentation Index
> Fetch the complete documentation index at: https://stratasync.blode.md/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

All hooks require a `SyncProvider`. They re-render automatically when data changes.

## useModel

Reads a single model by ID. Suspends while bootstrapping or loading a lazy model.

```tsx
import { Suspense } from "react";
import { useModel } from "@stratasync/react";

function TaskDetail({ taskId }: { taskId: string }) {
  const task = useModel<Task>("Task", taskId);

  if (!task) {
    return <div>Task not found</div>;
  }

  return <div>{task.title}</div>;
}

// Wrap in Suspense boundary
function TaskPage({ taskId }: { taskId: string }) {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <TaskDetail taskId={taskId} />
    </Suspense>
  );
}
```

**Signature:** `useModel<T>(modelName: string, id: string | null | undefined): T | null`

| Parameter   | Type                          | Description                                                    |
| ----------- | ----------------------------- | -------------------------------------------------------------- |
| `modelName` | `string`                      | Registered model name.                                         |
| `id`        | `string \| null \| undefined` | Model ID. Returns `null` immediately if `null` or `undefined`. |

**Return value:** `T | null`: the model instance or `null` if not found.

## useModelState

Non-Suspense alternative to `useModel`. Returns explicit loading and error states.

```tsx
import { useModelState } from "@stratasync/react";

function TaskDetail({ taskId }: { taskId: string }) {
  const {
    data: task,
    isLoading,
    isFound,
    error,
    refresh,
  } = useModelState<Task>("Task", taskId);

  if (isLoading) {
    return <div>Loading...</div>;
  }
  if (error) {
    return <div>Error: {error.message}</div>;
  }
  if (!isFound) {
    return <div>Task not found</div>;
  }

  return (
    <div>
      <h1>{task.title}</h1>
      <button onClick={refresh}>Refresh</button>
    </div>
  );
}
```

**Signature:** `useModelState<T>(modelName: string, id: string | null | undefined): UseModelResult<T>`

**UseModelResult:**

| Field       | Type                  | Description                                    |
| ----------- | --------------------- | ---------------------------------------------- |
| `data`      | `T \| null`           | The model instance.                            |
| `isLoading` | `boolean`             | Whether the model is being loaded.             |
| `isFound`   | `boolean`             | Whether the model was found (`data !== null`). |
| `error`     | `Error \| null`       | Any error that occurred during loading.        |
| `refresh`   | `() => Promise<void>` | Manually re-fetch the model.                   |

## useModelSuspense

Alias for `useModel` that makes the Suspense dependency explicit.

```tsx
import { useModelSuspense } from "@stratasync/react";

function TaskDetail({ taskId }: { taskId: string }) {
  const task = useModelSuspense<Task>("Task", taskId);
  // Same behavior as useModel
}
```

**Signature:** `useModelSuspense<T>(modelName: string, id: string): T | null`

## useQuery

Queries models with filtering, sorting, and pagination. Returns loading states explicitly.

```tsx
import { useQuery } from "@stratasync/react";

function TaskList({ projectId }: { projectId: string }) {
  const {
    data: tasks,
    isLoading,
    hasMore,
    totalCount,
    refresh,
  } = useQuery<Task>("Task", {
    where: (task) => task.projectId === projectId,
    orderBy: (a, b) => a.createdAt - b.createdAt,
    limit: 20,
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <p>
        Showing {tasks.length} of {totalCount} tasks
      </p>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
      {hasMore && <button onClick={refresh}>Load more</button>}
    </div>
  );
}
```

**Signature:** `useQuery<T>(modelName: string, options?: UseQueryOptions<T>): UseQueryResult<T>`

**UseQueryOptions** (extends `QueryOptions`):

| Option            | Type                     | Default | Description              |
| ----------------- | ------------------------ | ------- | ------------------------ |
| `where`           | `(item: T) => boolean`   | -       | Filter predicate.        |
| `orderBy`         | `(a: T, b: T) => number` | -       | Sort comparator.         |
| `limit`           | `number`                 | -       | Maximum results.         |
| `offset`          | `number`                 | -       | Results to skip.         |
| `includeArchived` | `boolean`                | `false` | Include archived items.  |
| `skip`            | `boolean`                | `false` | Skip the query entirely. |

**UseQueryResult:**

| Field        | Type                  | Description                             |
| ------------ | --------------------- | --------------------------------------- |
| `data`       | `T[]`                 | Query results.                          |
| `isLoading`  | `boolean`             | Whether the query is executing.         |
| `error`      | `Error \| null`       | Any error that occurred.                |
| `totalCount` | `number \| undefined` | Total matching count before pagination. |
| `hasMore`    | `boolean`             | Whether more results are available.     |
| `refresh`    | `() => Promise<void>` | Re-execute the query.                   |

## useQueryAll

Convenience wrapper around `useQuery` without pagination (`limit`/`offset`).

```tsx
import { useQueryAll } from "@stratasync/react";

function AllTasks() {
  const { data: tasks, isLoading } = useQueryAll<Task>("Task", {
    where: (t) => t.status !== "done",
    orderBy: (a, b) => a.title.localeCompare(b.title),
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return <TaskGrid tasks={tasks} />;
}
```

**Signature:** `useQueryAll<T>(modelName: string, options?: Omit<UseQueryOptions<T>, "limit" | "offset">): UseQueryResult<T>`

## useQueryCount

Returns the count of matching models without fetching them.

```tsx
import { useQueryCount } from "@stratasync/react";

function TaskCounter({ projectId }: { projectId: string }) {
  const { count, isLoading } = useQueryCount<Task>(
    "Task",
    (task) => task.projectId === projectId && task.status === "todo"
  );

  if (isLoading) {
    return <span>...</span>;
  }

  return <span>{count} tasks</span>;
}
```

**Signature:** `useQueryCount<T>(modelName: string, where?: (item: T) => boolean): { count: number; isLoading: boolean; error: Error | null }`

| Field       | Type            | Description                          |
| ----------- | --------------- | ------------------------------------ |
| `count`     | `number`        | Number of matching models.           |
| `isLoading` | `boolean`       | Whether the count is being computed. |
| `error`     | `Error \| null` | Any error that occurred.             |

## useConnectionState

Returns sync status, last sync ID, pending count, and error state.

```tsx
import { useConnectionState } from "@stratasync/react";

function SyncStatusBadge() {
  const { status, lastSyncId, backlog, error } = useConnectionState();

  if (error) {
    return <Badge color="red">Error: {error.message}</Badge>;
  }

  if (status === "bootstrapping") {
    return <Badge color="yellow">Bootstrapping...</Badge>;
  }

  if (status === "syncing") {
    return (
      <Badge color="green">
        Synced (ID: {lastSyncId}) {backlog > 0 && `- ${backlog} pending`}
      </Badge>
    );
  }

  return <Badge color="gray">{status}</Badge>;
}
```

**Signature:** `useConnectionState(): UseConnectionStateResult`

**UseConnectionStateResult:**

| Field        | Type              | Description                                                                                         |
| ------------ | ----------------- | --------------------------------------------------------------------------------------------------- |
| `status`     | `SyncClientState` | Current sync state: `"disconnected"`, `"connecting"`, `"bootstrapping"`, `"syncing"`, or `"error"`. |
| `lastSyncId` | `number`          | Last sync ID received from the server.                                                              |
| `backlog`    | `number`          | Number of pending (unsynced) transactions in the outbox.                                            |
| `error`      | `Error \| null`   | Last sync error, or `null` if no error.                                                             |

## useIsOffline

Returns `true` when the sync client's connection state is `"disconnected"`.

```tsx
import { useIsOffline } from "@stratasync/react";

function OfflineBanner() {
  const isOffline = useIsOffline();

  if (!isOffline) {
    return null;
  }

  return <div>You're offline. Changes will sync when you reconnect.</div>;
}
```

**Signature:** `useIsOffline(): boolean`

## usePendingCount

Returns the count of unsynced transactions.

```tsx
import { usePendingCount } from "@stratasync/react";

function PendingIndicator() {
  const { count, hasPending } = usePendingCount();

  if (!hasPending) {
    return <span>All changes saved</span>;
  }

  return <span>{count} changes pending sync</span>;
}
```

**Signature:** `usePendingCount(): UsePendingCountResult`

**UsePendingCountResult:**

| Field        | Type      | Description                     |
| ------------ | --------- | ------------------------------- |
| `count`      | `number`  | Number of pending transactions. |
| `hasPending` | `boolean` | `true` when `count > 0`.        |

## useSync

Trigger a manual sync cycle.

```tsx
import { useSync } from "@stratasync/react";

function SyncButton() {
  const { sync, isSyncing } = useSync();

  return (
    <button onClick={sync} disabled={isSyncing}>
      {isSyncing ? "Syncing..." : "Sync Now"}
    </button>
  );
}
```

**Signature:** `useSync(): { sync: () => Promise<void>; isSyncing: boolean }`

| Field       | Type                  | Description                                                   |
| ----------- | --------------------- | ------------------------------------------------------------- |
| `sync`      | `() => Promise<void>` | Triggers `client.syncNow()`. Guards against concurrent calls. |
| `isSyncing` | `boolean`             | `true` while a manual sync is in progress.                    |

## useSyncClient

Returns the full sync context: client instance, connection state, and derived flags.

```tsx
import { useSyncClient } from "@stratasync/react";

function SyncDebugPanel() {
  const {
    client,
    state,
    connectionState,
    lastSyncId,
    backlog,
    error,
    isReady,
    isSyncing,
    isOffline,
    clientId,
  } = useSyncClient();

  return (
    <pre>
      {JSON.stringify(
        {
          state,
          connectionState,
          lastSyncId,
          backlog,
          isReady,
          isOffline,
          clientId,
        },
        null,
        2
      )}
    </pre>
  );
}
```

**Signature:** `useSyncClient(): SyncContextValue`

**SyncContextValue:**

| Field             | Type              | Description                                            |
| ----------------- | ----------------- | ------------------------------------------------------ |
| `client`          | `SyncClient`      | The sync client instance.                              |
| `state`           | `SyncClientState` | Current sync state.                                    |
| `connectionState` | `ConnectionState` | Current connection state.                              |
| `lastSyncId`      | `number`          | Last sync ID from server.                              |
| `backlog`         | `number`          | Pending transaction count.                             |
| `error`           | `Error \| null`   | Last sync error.                                       |
| `isReady`         | `boolean`         | `true` when state is `"syncing"`.                      |
| `isSyncing`       | `boolean`         | `true` when state is `"syncing"` or `"bootstrapping"`. |
| `isOffline`       | `boolean`         | `true` when connection is `"disconnected"`.            |
| `clientId`        | `string`          | Client instance identifier.                            |

Throws an error if called outside of a `SyncProvider`.

## useSyncClientInstance

Returns the `SyncClient` instance for calling client methods directly.

```tsx
import { useSyncClientInstance } from "@stratasync/react";

function CreateTaskButton() {
  const client = useSyncClientInstance();

  const handleCreate = async () => {
    await client.create("Task", {
      title: "New task",
      status: "todo",
    });
  };

  return <button onClick={handleCreate}>Create Task</button>;
}
```

**Signature:** `useSyncClientInstance(): SyncClient`

## useSyncReady

`true` when bootstrapping is complete and the client is in `"syncing"` state. Gate UI rendering until data is available.

```tsx
import { useSyncReady } from "@stratasync/react";

function AppContent() {
  const isReady = useSyncReady();

  if (!isReady) {
    return <FullPageLoader />;
  }

  return <Dashboard />;
}
```

**Signature:** `useSyncReady(): boolean`

## useSyncState

Returns the current `SyncClientState` string value.

```tsx
import { useSyncState } from "@stratasync/react";

function StateIndicator() {
  const state = useSyncState();

  const labels: Record<string, string> = {
    disconnected: "Disconnected",
    connecting: "Connecting...",
    bootstrapping: "Loading data...",
    syncing: "Ready",
    error: "Error",
  };

  return <span>{labels[state] ?? state}</span>;
}
```

**Signature:** `useSyncState(): SyncClientState`

Returns one of: `"disconnected"`, `"connecting"`, `"bootstrapping"`, `"syncing"`, `"error"`.