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

# useAgent Hook

> React hook for connecting to Agents with state synchronization and RPC

## Overview

`useAgent` is a React hook for connecting to Agents via WebSocket. It provides real-time state synchronization, typed RPC method calls, and identity management.

```typescript theme={null}
import { useAgent } from "agents/react";

function Counter() {
  const [state, setState] = useState<{ count: number }>();

  const agent = useAgent<{ count: number }>({
    agent: "CounterAgent",
    name: "default",
    onStateUpdate: (newState) => setState(newState)
  });

  return (
    <div>
      <p>Count: {state?.count ?? 0}</p>
      <button onClick={() => agent.call("increment")}>
        Increment
      </button>
    </div>
  );
}
```

## Options

<ParamField path="options" type="UseAgentOptions<State>" required>
  <ParamField path="agent" type="string" required>
    Name of the agent class (e.g., "MyAgent" → "my-agent")
  </ParamField>

  <ParamField path="name" type="string" default="default">
    Name of the specific Agent instance
  </ParamField>

  <ParamField path="basePath" type="string">
    Full URL path - bypasses agent/name URL construction. Server must handle routing manually (e.g., with getAgentByName + fetch).
  </ParamField>

  <ParamField path="path" type="string">
    Additional path to append to the URL. Works with both standard routing and basePath.
  </ParamField>

  <ParamField path="query" type="QueryObject | (() => Promise<QueryObject>)">
    Query parameters - can be static object or async function
  </ParamField>

  <ParamField path="queryDeps" type="unknown[]">
    Dependencies for async query caching
  </ParamField>

  <ParamField path="cacheTtl" type="number" default="300000">
    Cache TTL in milliseconds for auth tokens/time-sensitive data (default: 5 minutes)
  </ParamField>

  <ParamField path="onStateUpdate" type="(state: State, source: 'server' | 'client') => void">
    Called when the Agent's state is updated
  </ParamField>

  <ParamField path="onStateUpdateError" type="(error: string) => void">
    Called when a state update fails (e.g., connection is readonly)
  </ParamField>

  <ParamField path="onMcpUpdate" type="(mcpServers: MCPServersState) => void">
    Called when MCP server state is updated
  </ParamField>

  <ParamField path="onIdentity" type="(name: string, agent: string) => void">
    Called when the server sends the agent's identity on connect
  </ParamField>

  <ParamField path="onIdentityChange" type="(oldName: string, newName: string, oldAgent: string, newAgent: string) => void">
    Called when identity changes on reconnect
  </ParamField>

  <ParamField path="enabled" type="boolean" default="true">
    Whether the connection should be enabled. Useful for conditional connections.
  </ParamField>
</ParamField>

## Return Value

Returns a PartySocket instance extended with:

<ResponseField name="agent" type="string" required>
  The agent class name (kebab-case)
</ResponseField>

<ResponseField name="name" type="string" required>
  The agent instance name
</ResponseField>

<ResponseField name="identified" type="boolean" required>
  Whether identity has been received from the server
</ResponseField>

<ResponseField name="ready" type="Promise<void>" required>
  Promise that resolves when identity is received
</ResponseField>

<ResponseField name="setState" type="(state: State) => void" required>
  Update the Agent's state from the client
</ResponseField>

<ResponseField name="call" type="(method: string, args?: unknown[], options?: StreamOptions) => Promise<T>" required>
  Call a method on the Agent via RPC
</ResponseField>

<ResponseField name="stub" type="AgentStub<T>" required>
  Typed RPC stub for calling Agent methods
</ResponseField>

## Basic Usage

### State Synchronization

```typescript theme={null}
import { useAgent } from "agents/react";
import { useState } from "react";

function Counter() {
  const [state, setState] = useState<{ count: number }>();

  const agent = useAgent<{ count: number }>({
    agent: "CounterAgent",
    name: "default",
    onStateUpdate: (newState) => setState(newState)
  });

  return (
    <div>
      <p>Count: {state?.count ?? 0}</p>
      <button onClick={() => agent.call("increment")}>
        Increment
      </button>
    </div>
  );
}
```

### Typed RPC with Stub

```typescript theme={null}
type CounterAgent = Agent<Env, { count: number }> & {
  increment(): Promise<number>;
  add(amount: number): Promise<number>;
};

function Counter() {
  const agent = useAgent<CounterAgent>({
    agent: "CounterAgent",
    name: "default"
  });

  const handleIncrement = async () => {
    // Fully typed!
    const newCount = await agent.stub.increment();
    console.log("New count:", newCount);
  };

  const handleAdd = async () => {
    const newCount = await agent.stub.add(5);
    console.log("New count:", newCount);
  };

  return (
    <div>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleAdd}>Add 5</button>
    </div>
  );
}
```

## Advanced Usage

### Custom Routing with basePath

```typescript theme={null}
function UserDashboard() {
  const [state, setState] = useState();

  // Server routes based on auth context
  const agent = useAgent({
    agent: "UserAgent",
    basePath: "user",
    onStateUpdate: setState,
    onIdentity: (name, agentClass) => {
      console.log(`Connected to ${agentClass} instance: ${name}`);
    }
  });

  return <div>User: {state?.username}</div>;
}
```

### Async Query Parameters

```typescript theme={null}
function AuthenticatedChat() {
  const agent = useAgent({
    agent: "ChatAgent",
    name: "room-123",
    query: async () => {
      const token = await getAuthToken();
      return { token };
    },
    queryDeps: [], // Re-run query when deps change
    cacheTtl: 60000 // Cache token for 1 minute
  });

  return <ChatUI agent={agent} />;
}
```

### Conditional Connection

```typescript theme={null}
function Chat({ enabled }: { enabled: boolean }) {
  const agent = useAgent({
    agent: "ChatAgent",
    name: "default",
    enabled // Only connect when enabled
  });

  if (!enabled) {
    return <p>Disconnected</p>;
  }

  return <ChatUI agent={agent} />;
}
```

### MCP Server Updates

```typescript theme={null}
function McpTools() {
  const [mcpServers, setMcpServers] = useState<MCPServersState>();

  const agent = useAgent({
    agent: "MyAgent",
    name: "default",
    onMcpUpdate: (servers) => setMcpServers(servers)
  });

  return (
    <div>
      <h3>Available Tools:</h3>
      <ul>
        {mcpServers?.tools.map(tool => (
          <li key={tool.name}>{tool.name}</li>
        ))}
      </ul>
    </div>
  );
}
```

### Identity Changes

```typescript theme={null}
function DynamicAgent() {
  const agent = useAgent({
    agent: "SessionAgent",
    basePath: "session",
    onIdentityChange: (oldName, newName) => {
      console.warn(`Session changed: ${oldName} → ${newName}`);
      // Handle session migration
    }
  });

  return <div>Session: {agent.name}</div>;
}
```

## Streaming RPC

### Streaming Text Generation

```typescript theme={null}
function Chat() {
  const [chunks, setChunks] = useState<string[]>([]);
  const [loading, setLoading] = useState(false);

  const agent = useAgent({
    agent: "ChatAgent",
    name: "default"
  });

  const handleSend = async (message: string) => {
    setLoading(true);
    setChunks([]);

    await agent.call("chat", [message], {
      onChunk: (chunk: { text: string }) => {
        setChunks(prev => [...prev, chunk.text]);
      },
      onDone: () => {
        setLoading(false);
      },
      onError: (error) => {
        console.error("Stream error:", error);
        setLoading(false);
      }
    });
  };

  return (
    <div>
      <div>{chunks.join("")}</div>
      <button onClick={() => handleSend("Hello")} disabled={loading}>
        Send
      </button>
    </div>
  );
}
```

## Connection State

### Wait for Ready

```typescript theme={null}
function MyComponent() {
  const agent = useAgent({
    agent: "MyAgent",
    name: "default"
  });

  useEffect(() => {
    agent.ready.then(() => {
      console.log("Connected to:", agent.name);
    });
  }, [agent]);

  return <div>Agent: {agent.identified ? agent.name : "Connecting..."}</div>;
}
```

### Connection Events

```typescript theme={null}
function ConnectionStatus() {
  const [status, setStatus] = useState("connecting");

  const agent = useAgent({
    agent: "MyAgent",
    name: "default",
    onOpen: () => setStatus("connected"),
    onClose: () => setStatus("disconnected"),
    onError: () => setStatus("error")
  });

  return <div>Status: {status}</div>;
}
```

## Query Caching

Async query results are cached to avoid re-fetching on every render:

```typescript theme={null}
function SecureChat({ userId }: { userId: string }) {
  const agent = useAgent({
    agent: "ChatAgent",
    name: "default",
    query: async () => {
      const token = await fetchToken(userId);
      return { token };
    },
    queryDeps: [userId], // Re-fetch when userId changes
    cacheTtl: 5 * 60 * 1000 // Cache for 5 minutes
  });

  return <ChatUI agent={agent} />;
}
```

### Cache Invalidation

* Cache is invalidated when `queryDeps` change
* Cache expires after `cacheTtl` milliseconds
* Cache is cleared on connection close (forces re-fetch on reconnect)

## Error Handling

### RPC Errors

```typescript theme={null}
function MyComponent() {
  const agent = useAgent({ agent: "MyAgent", name: "default" });

  const handleAction = async () => {
    try {
      await agent.call("riskyMethod");
    } catch (error) {
      console.error("RPC error:", error.message);
    }
  };

  return <button onClick={handleAction}>Action</button>;
}
```

### State Update Errors

```typescript theme={null}
function ReadonlyViewer() {
  const agent = useAgent({
    agent: "MyAgent",
    name: "viewer",
    onStateUpdateError: (error) => {
      console.error("Cannot update state:", error);
      // "Connection is readonly"
    }
  });

  // This will trigger onStateUpdateError if connection is readonly
  const handleUpdate = () => {
    agent.setState({ data: "test" });
  };

  return <button onClick={handleUpdate}>Update</button>;
}
```

## Best Practices

### Extract State to Custom Hook

```typescript theme={null}
function useCounterState() {
  const [state, setState] = useState<{ count: number }>();

  const agent = useAgent<{ count: number }>({
    agent: "CounterAgent",
    name: "default",
    onStateUpdate: setState
  });

  return { state, agent };
}

function Counter() {
  const { state, agent } = useCounterState();

  return (
    <div>
      <p>Count: {state?.count ?? 0}</p>
      <button onClick={() => agent.call("increment")}>
        Increment
      </button>
    </div>
  );
}
```

### Memoize Callbacks

```typescript theme={null}
function MyComponent() {
  const [state, setState] = useState();

  const handleStateUpdate = useCallback((newState) => {
    setState(newState);
  }, []);

  const agent = useAgent({
    agent: "MyAgent",
    name: "default",
    onStateUpdate: handleStateUpdate
  });

  return <div>{/* ... */}</div>;
}
```

### Type Safety

```typescript theme={null}
type MyAgentType = Agent<Env, State> & {
  myMethod(arg: string): Promise<number>;
};

function TypedComponent() {
  const agent = useAgent<MyAgentType>({
    agent: "MyAgent",
    name: "default"
  });

  // Fully typed!
  const result = await agent.stub.myMethod("hello");
  //    ^? number

  return <div />;
}
```

## Related

* [AgentClient](/api/agent-client) - Vanilla JavaScript client
* [@callable](/api/callable) - Define callable methods
* [Agent Class](/api/agent-class) - Agent base class
* [State Management](/api/state) - State synchronization
