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

# Hook reference

> useCommentThread is the same store in React and Vue. Optimistic writes, pending comments, and ApiError codes.

`useCommentThread(type, id, { sort?, perPage?, client? })` is the store both batteries-included threads run on.

* **React** spreads state into the return value (`comments`, `status`, …).
* **Vue** nests it under `state` (`state.comments`, `state.status`).

`type` is the commentable alias. `id` is the record key.

## State

| Field      | Meaning                                                     |
| ---------- | ----------------------------------------------------------- |
| `comments` | Loaded comments (replies nested when the API included them) |
| `status`   | `idle` \| `loading` \| `loadingMore` \| `error`             |
| `error`    | Last `ApiError`, or `null`                                  |
| `sort`     | Active sort order                                           |
| `page`     | Current page                                                |
| `lastPage` | Final page for the current sort                             |
| `hasMore`  | Whether another page exists                                 |
| `total`    | Total matching comments                                     |

A comment held for moderation is decorated with `pending: true` (`pending ?? !is_approved`) so you can show it to its author while it waits. `comment.can.update` and `comment.can.delete` mirror `CommentPolicy`. `comment.can.pin` is true for parent comments when the current user is a moderator and pins are enabled.

## Actions

All return promises. Writes apply **optimistically** and roll back if the request fails.

| Action                                | Notes                                                      |
| ------------------------------------- | ---------------------------------------------------------- |
| `post(body, parentId?)`               | Omit `parentId` for a root comment                         |
| `edit(commentId, body)`               | Author only                                                |
| `remove(commentId)`                   | Author only                                                |
| `toggleLike(commentId)`               | Works for guests (IP + user agent) when enabled            |
| `report(commentId, reason, details?)` | `details` matches the Livewire form (`additional_details`) |
| `loadReplies(parentId)`               | Fetches a nested page                                      |
| `loadMore()`                          | Appends the next page                                      |
| `goToPage(page)`                      | Replaces the page, like the Blade paginator                |
| `setSort(sort)`                       | Resets and reloads the thread                              |
| `reload()`                            | Refetches page one                                         |

The result also includes `store` (`ThreadStore`) if you need `subscribeToThread()` for [Reverb](/realtime).

## `CommentifyClient`

```ts theme={null}
import { CommentifyClient, ApiError } from "@commentify/core";

const client = new CommentifyClient({
  baseUrl: "",           // origin; default current origin
  prefix: "commentify/api/v1",
  token: undefined,      // Bearer token; skips cookie credentials
  onError: (error) => {
    if (error.code === "spam_rejected") toast("That looked like spam.");
  },
});
```

Methods: `listComments`, `createComment`, `updateComment`, `deleteComment`, `toggleLike`, `report`, `ui`, `uploadMedia`, `pinComment`, `unpinComment`, `previewMarkdown`, `searchUsers`, `listReplies`.

`createComment` accepts optional `{ guest_name, guest_email }`. Guest likes/reports do not need extra fields — the server keys on IP + user agent.

## Errors

Failures throw `ApiError` with `status`, `message`, `errors`, and a stable `code`. Switch on `code`, not the message:

| `code`             | HTTP | When                                                         |
| ------------------ | ---- | ------------------------------------------------------------ |
| `spam_rejected`    | 422  | Pipeline **deny** (save aborted)                             |
| `read_only`        | 423  | `core.read_only`                                             |
| `comment_banned`   | 403  | `comment_banned_until` in the future                         |
| `unauthenticated`  | 401  | User search without a user; other writes may use a plain 401 |
| `already_reported` | 409  | Same visitor already reported this comment                   |
| `nesting_disabled` | 422  | Reply posted while `comment_nesting` is false                |
| `parent_mismatch`  | 422  | `parent_id` belongs to another thread                        |
| `cannot_pin_reply` | 422  | Pin on a non-parent comment                                  |

See [API](/api) for request shapes.
