Skip to main content

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.

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.
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

options
UseAgentOptions<State>
required
agent
string
required
Name of the agent class (e.g., “MyAgent” → “my-agent”)
name
string
default:"default"
Name of the specific Agent instance
basePath
string
Full URL path - bypasses agent/name URL construction. Server must handle routing manually (e.g., with getAgentByName + fetch).
path
string
Additional path to append to the URL. Works with both standard routing and basePath.
query
QueryObject | (() => Promise<QueryObject>)
Query parameters - can be static object or async function
queryDeps
unknown[]
Dependencies for async query caching
cacheTtl
number
default:"300000"
Cache TTL in milliseconds for auth tokens/time-sensitive data (default: 5 minutes)
onStateUpdate
(state: State, source: 'server' | 'client') => void
Called when the Agent’s state is updated
onStateUpdateError
(error: string) => void
Called when a state update fails (e.g., connection is readonly)
onMcpUpdate
(mcpServers: MCPServersState) => void
Called when MCP server state is updated
onIdentity
(name: string, agent: string) => void
Called when the server sends the agent’s identity on connect
onIdentityChange
(oldName: string, newName: string, oldAgent: string, newAgent: string) => void
Called when identity changes on reconnect
enabled
boolean
default:"true"
Whether the connection should be enabled. Useful for conditional connections.

Return Value

Returns a PartySocket instance extended with:
agent
string
required
The agent class name (kebab-case)
name
string
required
The agent instance name
identified
boolean
required
Whether identity has been received from the server
ready
Promise<void>
required
Promise that resolves when identity is received
setState
(state: State) => void
required
Update the Agent’s state from the client
call
(method: string, args?: unknown[], options?: StreamOptions) => Promise<T>
required
Call a method on the Agent via RPC
stub
AgentStub<T>
required
Typed RPC stub for calling Agent methods

Basic Usage

State Synchronization

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

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

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

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

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

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

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

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

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

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:
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

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

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

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

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

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 />;
}

Build docs developers (and LLMs) love