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

# React Hooks

> Drop-in React hooks for document viewing, chat, and structured extraction.

## Install

```bash theme={null}
npm install @okrapdf/react @okrapdf/runtime
```

`@okrapdf/react` is a peer of `@okrapdf/runtime` — install both.

## OkraProvider

Wrap your app (or a subtree) with `OkraProvider` to supply the API key to all hooks.

```tsx theme={null}
import { OkraProvider } from '@okrapdf/react';

export default function App({ children }) {
  return (
    <OkraProvider apiKey={process.env.NEXT_PUBLIC_OKRA_API_KEY}>
      {children}
    </OkraProvider>
  );
}
```

### Options

| Prop      | Type     | Default                   | Description                         |
| --------- | -------- | ------------------------- | ----------------------------------- |
| `apiKey`  | `string` | —                         | API key (`okra_...`). **Required.** |
| `baseUrl` | `string` | `https://api.okrapdf.com` | Override for self-hosted or dev.    |

## useDocument

Fetch document status and pages.

```tsx theme={null}
import { useDocument } from '@okrapdf/react';

function DocumentViewer({ id }: { id: string }) {
  const { status, pages, isLoading, error } = useDocument(id);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <p>Phase: {status.phase} ({status.pagesCompleted}/{status.pagesTotal} pages)</p>
      {pages.map((p) => (
        <div key={p.page}>
          <h3>Page {p.page}</h3>
          <pre>{p.content}</pre>
        </div>
      ))}
    </div>
  );
}
```

### Returns

| Field       | Type             | Description                                     |
| ----------- | ---------------- | ----------------------------------------------- |
| `status`    | `DocumentStatus` | Current processing status (phase, page counts). |
| `pages`     | `Page[]`         | Extracted page content and entities.            |
| `isLoading` | `boolean`        | True while initial fetch is in progress.        |
| `error`     | `Error \| null`  | Error if the fetch failed.                      |

The hook polls automatically while the document is still processing, then stops once extraction is complete.

## useChat

Stream chat messages with a document.

```tsx theme={null}
import { useChat } from '@okrapdf/react';

function ChatPanel({ id }: { id: string }) {
  const { messages, send, isStreaming } = useChat(id);

  return (
    <div>
      {messages.map((m, i) => (
        <div key={i} className={m.role === 'user' ? 'text-right' : ''}>
          <p>{m.content}</p>
        </div>
      ))}
      <form onSubmit={(e) => {
        e.preventDefault();
        const input = e.currentTarget.elements.namedItem('q') as HTMLInputElement;
        send(input.value);
        input.value = '';
      }}>
        <input name="q" placeholder="Ask about this document..." disabled={isStreaming} />
        <button type="submit" disabled={isStreaming}>Send</button>
      </form>
    </div>
  );
}
```

### Returns

| Field         | Type                      | Description                             |
| ------------- | ------------------------- | --------------------------------------- |
| `messages`    | `ChatMessage[]`           | Chat history (`{ role, content }`).     |
| `send`        | `(query: string) => void` | Send a new message.                     |
| `isStreaming` | `boolean`                 | True while the assistant is responding. |
| `error`       | `Error \| null`           | Error if the stream failed.             |

## useStructuredOutput

Extract typed data from a document using a Zod schema.

```tsx theme={null}
import { useStructuredOutput } from '@okrapdf/react';
import { Invoice } from '@okrapdf/schemas';

function InvoiceExtractor({ id }: { id: string }) {
  const { data, meta, isLoading, error, extract } = useStructuredOutput(id, Invoice);

  return (
    <div>
      <button onClick={() => extract('Extract all invoice fields')} disabled={isLoading}>
        Extract
      </button>

      {data && (
        <div>
          <p>Vendor: {data.vendor}</p>
          <p>Total: ${data.total}</p>
          <p>Confidence: {(meta?.confidence ?? 0) * 100}%</p>
          <table>
            <thead><tr><th>Item</th><th>Amount</th></tr></thead>
            <tbody>
              {data.lineItems.map((item, i) => (
                <tr key={i}><td>{item.description}</td><td>${item.amount}</td></tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}
```

### Returns

| Field       | Type                           | Description                                                    |
| ----------- | ------------------------------ | -------------------------------------------------------------- |
| `data`      | `T \| null`                    | Extracted data matching the schema, or null before extraction. |
| `meta`      | `StructuredOutputMeta \| null` | Confidence score, model info, duration.                        |
| `isLoading` | `boolean`                      | True while extraction is running.                              |
| `error`     | `Error \| null`                | Error if extraction failed.                                    |
| `extract`   | `(query: string) => void`      | Trigger extraction with a natural-language prompt.             |

## Full example

Server-side upload, client-side rendering with hooks.

```tsx theme={null}
// app/api/upload/route.ts (server)
import { createOkra } from '@okrapdf/runtime';

const okra = createOkra({ apiKey: process.env.OKRA_API_KEY });

export async function POST(req: Request) {
  const form = await req.formData();
  const file = form.get('file') as File;
  const session = await okra.sessions.create(file, { wait: true });

  return Response.json({ id: session.id });
}
```

```tsx theme={null}
// components/DocumentView.tsx (client)
'use client';

import { OkraProvider, useDocument, useChat, useStructuredOutput } from '@okrapdf/react';
import { Invoice } from '@okrapdf/schemas';

function DocumentView({ id }: { id: string }) {
  const { status, pages } = useDocument(id);
  const { messages, send, isStreaming } = useChat(id);
  const { data, extract, isLoading } = useStructuredOutput(id, Invoice);

  return (
    <div className="grid grid-cols-2 gap-4">
      {/* Left: pages */}
      <div>
        <p>{status.phase} — {status.pagesCompleted} pages</p>
        {pages.map((p) => <pre key={p.page}>{p.content}</pre>)}
      </div>

      {/* Right: chat + extraction */}
      <div>
        <div>
          {messages.map((m, i) => <p key={i}><b>{m.role}:</b> {m.content}</p>)}
          <input
            onKeyDown={(e) => e.key === 'Enter' && send(e.currentTarget.value)}
            placeholder="Ask..."
            disabled={isStreaming}
          />
        </div>

        <button onClick={() => extract('Extract invoice fields')} disabled={isLoading}>
          Extract Invoice
        </button>
        {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
      </div>
    </div>
  );
}

export default function Wrapper({ id }: { id: string }) {
  return (
    <OkraProvider apiKey={process.env.NEXT_PUBLIC_OKRA_API_KEY!}>
      <DocumentView id={id} />
    </OkraProvider>
  );
}
```
