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

# Get started with the Chat SDK in React

> Install the Chat SDK and build a working Typewise chat in a React app

Build a working chat in React: a message list, a typing indicator, and a
composer, connected to your Typewise AI Agent. You'll have it running in a few
minutes and can style it however you like afterwards.

> [!NOTE]
>
> Not using React? Follow [Plain HTML and
> JavaScript](https://docs.typewise.app/guides/channels/chat-sdk/html.md) or see [Other
> frameworks](https://docs.typewise.app/guides/channels/chat-sdk/other-frameworks.md). The concepts are the
> same.

## Prerequisites

* A React 18 or later app
* Your **client ID**, from **Behavior → Channels → Chat → Installation**
* If your chat has a **Domain allowlist**, `localhost` added to it. While the
  list is empty, every origin is allowed.

Using an AI coding tool? Copy this prompt, or open it in Cursor.

Add a Typewise chat to this React app with the headless Chat SDK
(@typewise/chat-sdk). Follow
[https://docs.typewise.app/guides/channels/chat-sdk/getting-started](https://docs.typewise.app/guides/channels/chat-sdk/getting-started.md). Create one
client at module scope with createClient and my client ID (YOUR\_CLIENT\_ID),
read its state with useSyncExternalStore(chat.subscribe, chat.getState,
chat.getState), and call chat.resume() once on mount. Render state.messages
(keyed by dedupeKey ?? id), a typing indicator from state.activity, and a
composer that calls chat.sendMessage(text). Show failed messages without
retrying them, and offer chat.startNewConversation() when
state.conversation.isClosed is true. Match the app's existing components and
styles.

## Build the chat

You'll add three files:

**src**

**chat.ts**

**useChat.ts**

**Chat.tsx**

**1. Install the SDK**

```bash npm theme={null}
npm install @typewise/chat-sdk
```

```bash pnpm theme={null}
pnpm add @typewise/chat-sdk
```

```bash yarn theme={null}
yarn add @typewise/chat-sdk
```

**2. Create the client**

Create one client at module scope and import it wherever you need it. Each
client holds one visitor session and one real-time connection.

```typescript src/chat.ts theme={null}
import { createClient } from "@typewise/chat-sdk";

export const chat = createClient({ clientId: "YOUR_CLIENT_ID" });
```

**3. Connect it to React**

The client is an external store, so `useSyncExternalStore` is all you need.
`resume()` restores the visitor's open conversation and loads the greetings.

```typescript src/useChat.ts highlight={5,8} theme={null}
import { useEffect, useSyncExternalStore } from "react";
import { chat } from "./chat";

export function useChat() {
  const state = useSyncExternalStore(chat.subscribe, chat.getState, chat.getState);

  useEffect(() => {
    chat.resume().catch((error) => console.warn("[typewise]", error));
  }, []);

  return state;
}
```

**4. Render the conversation**

```tsx src/Chat.tsx theme={null}
import { useState, type FormEvent } from "react";
import type { Activity } from "@typewise/chat-sdk";
import { chat } from "./chat";
import { useChat } from "./useChat";

export function Chat() {
  const { messages, activity, conversation } = useChat();
  const [draft, setDraft] = useState("");

  function send(event: FormEvent) {
    event.preventDefault();
    const text = draft.trim();
    if (!text) return;
    setDraft("");
    // A failed send stays in `messages` with status "failed".
    chat.sendMessage(text).catch(() => {});
  }

  return (
    <section>
      <ol aria-live="polite">
        {messages.map((message) => (
          <li key={message.dedupeKey ?? message.id} className={message.role}>
            {message.role === "agent" && <strong>{message.authorName}: </strong>}
            {message.text}
            {message.status === "failed" && <small> Not delivered</small>}
          </li>
        ))}
        {activity.kind !== "idle" && <li className="activity">{activityLabel(activity)}</li>}
      </ol>

      {conversation?.isClosed ? (
        <button onClick={() => chat.startNewConversation().catch(() => {})}>
          Start a new conversation
        </button>
      ) : (
        <form onSubmit={send}>
          <input value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="Ask a question" />
          <button type="submit" disabled={!draft.trim()}>Send</button>
        </form>
      )}
    </section>
  );
}

function activityLabel(activity: Activity) {
  if (activity.kind === "ai_thinking") return activity.text ?? "Thinking…";
  if (activity.kind === "agent_typing") return "Typing…";
  return "Waiting for a team member…";
}
```

Render `<Chat />` anywhere in your app.

**5. Send a test message**

Start your dev server, open the page, and ask a question. The greeting
appears first, then your message, a "Thinking…" indicator, and the AI
Agent's reply. Reload the page: the conversation is restored.

## What's happening

* **`messages`** holds greetings, history, and live messages in order. Your own
  message appears at once with `status: "sending"`, then switches to `"sent"`.
* **Message keys.** A message's `id` changes when the server confirms it. Its
  `dedupeKey` stays the same, so it makes a stable React key.
* **`activity`** tells you what to show as a typing indicator: the AI Agent
  working, a Human Agent typing, or the conversation waiting for one.
* **`conversation`** is `null` until the first message. Once a conversation
  closes, `isClosed` is `true` and the visitor can start a new one.

For every field, see
[Render messages and activity](https://docs.typewise.app/guides/channels/chat-sdk/render-messages.md).

## Use it with Next.js

`createClient` doesn't touch `window` or storage at import time, so the module
is safe to import on the server. Add `"use client"` at the top of `Chat.tsx`,
because it uses hooks.

## Before you go live

* **One client per app.** Create it at module scope, not inside a component, so
  re-renders don't open new connections.
* **Catch errors.** Methods reject with a
  [`TypewiseError`](https://docs.typewise.app/guides/channels/chat-sdk/api-reference#errors). Listen to
  `chat.on("error", …)` for background failures.
* **Don't retry sends automatically.** Sending isn't idempotent, so the SDK
  never retries. Show failed messages and let the visitor send again.
* **Send page context.** Tell the AI Agent what the visitor is looking at with
  [context variables](https://docs.typewise.app/guides/channels/chat-sdk/page-context.md).
* **Verify identity.** If actions or lookups use the **Trusted only** check,
  pass [signed variables](https://docs.typewise.app/guides/channels/chat-widget/identity-verification.md).
* **Forget the visitor on logout.** Call `chat.resetSession()` so the next
  person on the device doesn't see the conversation.
* **Allow your domains.** Add production origins to the **Domain allowlist**,
  and to your
  [Content Security Policy](https://docs.typewise.app/guides/channels/chat-widget/content-security-policy.md#chat-sdk)
  if you have one.

## Next steps

> **[Share page context](https://docs.typewise.app/guides/channels/chat-sdk/page-context.md)**
>
> Tell the AI Agent what the visitor is doing

> **[Render messages and activity](https://docs.typewise.app/guides/channels/chat-sdk/render-messages.md)**
>
> Quick actions, Human Agent handoff, and closed conversations

> **[Attachments, surveys, and verification](https://docs.typewise.app/guides/channels/chat-sdk/attachments-surveys-verification.md)**
>
> Files, satisfaction ratings, and email codes

> **[Examples](https://docs.typewise.app/guides/channels/chat-sdk/examples.md)**
>
> Complete apps you can copy


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.