Skip to content

React Examples

The UClaw SDK exports React hooks from @uclaw/sdk/react. Use useApp for app-level agent directory state, and useAgent for one active agent's chat.

Backend: UClaw API Handler

Create an API Route to automatically serve client tokens. The SDK provides AppClient.handler to handle standard Web Request/Response objects.

In Next.js App Router, create a catch-all file at app/api/uclaw/[...all]/route.ts:

typescript
import { AppClient } from "@uclaw/sdk";

const app = new AppClient({
  apiKey: process.env.UCLAW_API_KEY,
});

// Automatically handles POST requests to /api/uclaw/client-tokens
export const POST = (request: Request) => app.handler(request);

Frontend: App Shell

tsx
import { useApp } from "@uclaw/sdk/react";
import { useState } from "react";

export function ChatApp() {
  const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
  const { agents, createAgent, deleteAgent, status } = useApp({
    appId: "default",
  });

  async function handleNewChat() {
    const agent = await createAgent({
      title: "Travel planner",
      config: {
        modelTier: "fast",
        instructions: "You are a friendly travel planner.",
      },
    });
    setActiveAgentId(agent.id);
  }

  return (
    <div className="flex gap-4">
      <aside className="w-64 border-r p-4">
        <p>App status: {status}</p>
        <button onClick={handleNewChat}>New Chat</button>
        {agents.map((agent) => (
          <div key={agent.id}>
            <button onClick={() => setActiveAgentId(agent.id)}>{agent.title}</button>
            <button onClick={() => deleteAgent(agent.id)}>Delete</button>
          </div>
        ))}
      </aside>
      <main className="flex-1">{activeAgentId && <ChatPane agentId={activeAgentId} />}</main>
    </div>
  );
}

Frontend: Active Agent Chat

tsx
import { useAgent } from "@uclaw/sdk/react";
import { useState } from "react";

export function ChatPane({ agentId }: { agentId: string }) {
  const [input, setInput] = useState("");
  const { chat, status } = useAgent({
    agentId,
  });

  function handleSend(event: React.FormEvent) {
    event.preventDefault();
    if (!input.trim() || chat.isStreaming) return;

    chat.sendMessage({
      role: "user",
      parts: [{ type: "text", text: input.trim() }],
    });
    setInput("");
  }

  return (
    <div className="flex h-full flex-col p-4">
      <p>Agent status: {status}</p>
      <div className="flex-1 space-y-4 overflow-y-auto">
        {chat.messages.map((message) => {
          const text = message.parts
            .filter((part) => part.type === "text")
            .map((part) => part.text)
            .join("");

          return (
            <div key={message.id} className={message.role === "user" ? "text-right" : ""}>
              {text}
            </div>
          );
        })}
      </div>
      <form onSubmit={handleSend} className="mt-4 flex gap-2">
        <input
          value={input}
          onChange={(event) => setInput(event.target.value)}
          disabled={status !== "connected"}
        />
        {chat.isStreaming ? (
          <button type="button" onClick={chat.stop}>
            Stop
          </button>
        ) : (
          <button type="submit">Send</button>
        )}
      </form>
    </div>
  );
}