React Hooks
Call your API routes from React with useAPI, useWebsocket, and useEventStream
Call your API routes from React components using type-safe hooks from @agentuity/react.
Non-React Alternative
For non-React apps (Vue, Svelte, vanilla JS) or server-side code, use the RPC Client from @agentuity/frontend instead.
Installation
bun add @agentuity/reactBasic Usage with useAPI
The useAPI hook calls your API routes with full type safety:
import { AgentuityProvider, useAPI } from '@agentuity/react';
function ChatForm() {
const { invoke, isLoading, data, error } = useAPI('POST /api/chat');
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const message = formData.get('message') as string;
try {
await invoke({ message });
} catch (err) {
// Error is also available via the error state
console.error('API call failed:', err);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="message" placeholder="Type a message..." disabled={isLoading} />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send'}
</button>
{error && <p>Error: {error.message}</p>}
{data && <p>Response: {data.response}</p>}
</form>
);
}
export default function App() {
return (
<AgentuityProvider>
<ChatForm />
</AgentuityProvider>
);
}Return values:
| Property | Type | Description | Methods |
|---|---|---|---|
invoke | (input) => Promise<TOutput> | Execute the request | POST, PUT, PATCH, DELETE |
refetch | () => Promise<void> | Manually refetch data | GET |
data | TOutput | undefined | Last successful response | All |
error | Error | null | Last error, if any | All |
isLoading | boolean | True during initial load | All |
isFetching | boolean | True during any fetch (including refetches) | All |
isSuccess | boolean | True after successful completion | All |
isError | boolean | True if request failed | All |
reset | () => void | Reset state to initial values | All |
GET requests auto-fetch on mount. POST/PUT/PATCH/DELETE require calling invoke().
useAPI Options
Pass an options object instead of a route string for advanced configuration:
const { data, refetch } = useAPI({
route: 'GET /api/users',
staleTime: 30000, // Data stays fresh for 30 seconds
refetchInterval: 60000, // Auto-refetch every 60 seconds
enabled: isReady, // Only fetch when condition is true
onSuccess: (data) => console.log('Fetched:', data),
onError: (err) => console.error('Failed:', err),
});| Option | Type | Default | Description |
|---|---|---|---|
route | string | - | Route key (e.g., 'GET /api/users') |
query | URLSearchParams | Record<string, string> | - | Query parameters |
headers | Record<string, string> | - | Additional request headers |
enabled | boolean | true (GET), false (others) | Control when request executes |
staleTime | number | 0 | Milliseconds data stays fresh before refetching |
refetchInterval | number | - | Auto-refetch interval in milliseconds |
onSuccess | (data) => void | - | Callback on successful request |
onError | (error) => void | - | Callback on failed request |
Streaming with useAPI
For streaming routes, useAPI accumulates chunks and provides transform callbacks:
const { data, isLoading } = useAPI({
route: 'POST /api/stream',
input: { prompt: 'Hello' },
delimiter: '\n', // Split chunks by newline (default)
onChunk: (chunk) => {
console.log('Received chunk:', chunk);
return chunk; // Can transform before accumulation
},
});
// data is TOutput[] - array of all received chunks| Option | Type | Description |
|---|---|---|
delimiter | string | Delimiter for splitting stream chunks (default: \n) |
onChunk | (chunk) => chunk | Transform each chunk before accumulation |
Real-Time with useWebsocket
For bidirectional real-time communication, use useWebsocket:
import { useWebsocket } from '@agentuity/react';
function RealtimeChat() {
const { isConnected, send, messages, clearMessages } = useWebsocket('/api/chat', {
maxMessages: 100, // Keep last 100 messages
});
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Connecting...'}</p>
<ul>
{messages.map((msg, i) => (
<li key={i}>{JSON.stringify(msg)}</li>
))}
</ul>
<button onClick={() => send({ message: 'Hello!' })}>Send Hello</button>
<button onClick={clearMessages}>Clear</button>
</div>
);
}You can also use data for only the most recent message:
import { useWebsocket } from '@agentuity/react';
import { useEffect } from 'react';
function LatestUpdate() {
const { isConnected, send, data } = useWebsocket('/api/updates');
useEffect(() => {
if (data) {
console.log('Latest update:', data);
}
}, [data]);
return <p>Latest: {data ? JSON.stringify(data) : 'Waiting...'}</p>;
}Auto-Reconnection
WebSocket connections automatically reconnect with exponential backoff if the connection drops. Messages sent while disconnected are queued and sent when the connection is restored.
Return values:
| Property | Type | Description |
|---|---|---|
isConnected | boolean | True when WebSocket is open |
send | (data: TInput) => void | Send a message |
data | TOutput | undefined | Last received message |
messages | TOutput[] | Array of all received messages |
clearMessages | () => void | Clear the messages array |
error | Error | null | Connection or message error |
isError | boolean | True if an error occurred |
readyState | number | WebSocket state (0=connecting, 1=open, 2=closing, 3=closed) |
close | () => void | Close the connection |
reset | () => void | Clear error state |
Options:
| Option | Type | Description |
|---|---|---|
query | URLSearchParams | Query parameters to append to the WebSocket URL |
subpath | string | Subpath to append to the WebSocket path |
signal | AbortSignal | AbortSignal to cancel the connection |
maxMessages | number | Maximum messages to keep in messages array (oldest removed when exceeded) |
Streaming with useEventStream
For one-way streaming from server to client, use Server-Sent Events:
import { useEventStream } from '@agentuity/react';
function LiveStatus() {
const { isConnected, data, error } = useEventStream('/api/status');
if (!isConnected) {
return <p>Connecting to status feed...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<div>
<p>Live Status: {data?.status ?? 'Waiting for update...'}</p>
<p>Last updated: {data?.timestamp ?? '-'}</p>
</div>
);
}When to Use SSE
Use useEventStream when you only need server-to-client updates (e.g., progress indicators, live dashboards, notifications). For bidirectional communication, use useWebsocket.
Return values:
| Property | Type | Description |
|---|---|---|
isConnected | boolean | True when EventStream is open |
data | TOutput | undefined | Last received event data |
error | Error | null | Connection error |
isError | boolean | True if an error occurred |
readyState | number | EventSource state (0=connecting, 1=open, 2=closed) |
close | () => void | Close the connection |
reset | () => void | Clear error state |
Options: Accepts query, subpath, and signal options, same as useWebsocket.
Choosing the Right Hook
| Hook | Use Case | Direction | Examples |
|---|---|---|---|
useAPI | Request/response | One-time | Send a message, fetch user data, submit a form |
useWebsocket | Bidirectional streaming | Client ↔ Server | Live chat, multiplayer sync, shared editing |
useEventStream | Server push | Server → Client | AI token streaming, build logs, live metrics |
Request Options
Pass options to the hook for customizing requests. Options like query and headers are set when calling the hook, not when invoking:
// Options are passed to useAPI, not invoke
const { invoke } = useAPI({
route: 'POST /api/chat',
query: new URLSearchParams({ version: '2' }),
headers: { 'X-Custom-Header': 'value' },
});
// invoke only takes the input data
await invoke({ message: 'Hello' });For dynamic query parameters, create a new hook instance:
function SearchResults({ query }: { query: string }) {
const { data, isLoading } = useAPI({
route: 'GET /api/search',
query: { q: query },
});
return <div>{isLoading ? 'Searching...' : JSON.stringify(data)}</div>;
}Auth State with useAuth
Access authentication state for protected components or custom auth logic:
import { useAuth } from '@agentuity/react';
function ProtectedContent() {
const { isAuthenticated, authLoading } = useAuth();
if (authLoading) {
return <p>Loading...</p>;
}
if (!isAuthenticated) {
return <p>Please sign in to continue.</p>;
}
return <p>Welcome! You have access.</p>;
}Return values:
| Property | Type | Description |
|---|---|---|
isAuthenticated | boolean | True when auth token is set and not loading |
authLoading | boolean | True while auth state is initializing |
authHeader | string | null | The current Authorization header value |
setAuthHeader | (token: string | null) => void | Manually set the auth header |
setAuthLoading | (loading: boolean) => void | Control the loading state |
useAuth vs useAgentuity
useAuth provides auth-specific state. For non-auth context like baseUrl, use useAgentuity() instead. See Authentication for integrating with identity providers.
Next Steps
- Provider Setup: Configure
AgentuityProviderfor deployments - Advanced Hooks: Custom handlers for WebSocket and SSE
- Deployment Scenarios: Choose where your frontend lives
Need Help?
Join our Community for assistance or just to hang with other humans building agents.
Send us an email at hi@agentuity.com if you'd like to get in touch.
Please Follow us on
If you haven't already, please Signup for your free account now and start building your first agent!