React hooks for building AI-powered chat, completion, and structured object streaming interfaces
npx @tessl/cli install tessl/npm-ai-sdk--react@2.0.00
# AI SDK React
1
2
React hooks for building AI-powered chat interfaces, text completions, and structured object streaming. Part of the Vercel AI SDK ecosystem, this package provides optimized state management, real-time streaming, and type-safe APIs for integrating AI capabilities into React applications.
3
4
## Package Information
5
6
- **Package Name**: @ai-sdk/react
7
- **Installation**: `npm install @ai-sdk/react`
8
9
## Imports
10
11
```typescript
12
import { useChat, useCompletion, experimental_useObject } from '@ai-sdk/react';
13
```
14
15
## Core Hooks
16
17
- **useChat**: Multi-turn conversational AI with message history, streaming, and tool calls
18
- **useCompletion**: Single-turn text completion with streaming and form helpers
19
- **experimental_useObject**: Streams structured objects validated with Zod schemas
20
21
All hooks use React's `useSyncExternalStore` for state management and SWR for caching, enabling state sharing across components with the same ID.
22
23
## Quick Start
24
25
```typescript
26
import { useChat } from '@ai-sdk/react';
27
28
function ChatComponent() {
29
const { messages, sendMessage, status, stop, error } = useChat();
30
31
return (
32
<div>
33
{messages.map((message) => (
34
<div key={message.id}>
35
{message.parts.map((part) =>
36
part.type === 'text' && <span key={part.text}>{part.text}</span>
37
)}
38
</div>
39
))}
40
<button
41
onClick={() => sendMessage({ parts: [{ type: 'text', text: 'Hello!' }] })}
42
disabled={status === 'streaming'}
43
>
44
Send
45
</button>
46
{status === 'streaming' && <button onClick={stop}>Stop</button>}
47
{error && <div>Error: {error.message}</div>}
48
</div>
49
);
50
}
51
```
52
53
## API Reference
54
55
### useChat
56
57
```typescript { .api }
58
function useChat<UI_MESSAGE extends UIMessage = UIMessage>(
59
options?: UseChatOptions<UI_MESSAGE>
60
): UseChatHelpers<UI_MESSAGE>;
61
62
type UseChatOptions<UI_MESSAGE extends UIMessage> = (
63
| { chat: Chat<UI_MESSAGE> }
64
| ChatInit<UI_MESSAGE>
65
) & {
66
experimental_throttle?: number;
67
resume?: boolean;
68
};
69
70
interface UseChatHelpers<UI_MESSAGE extends UIMessage> {
71
readonly id: string;
72
messages: UI_MESSAGE[];
73
setMessages: (messages: UI_MESSAGE[] | ((messages: UI_MESSAGE[]) => UI_MESSAGE[])) => void;
74
sendMessage: (message?: CreateUIMessage<UI_MESSAGE> | { text: string; files?: FileList | FileUIPart[] }, options?: ChatRequestOptions) => Promise<void>;
75
regenerate: (options?: { messageId?: string } & ChatRequestOptions) => Promise<void>;
76
stop: () => Promise<void>;
77
resumeStream: (options?: ChatRequestOptions) => Promise<void>;
78
addToolResult: <TOOL extends keyof InferUIMessageTools<UI_MESSAGE>>(
79
options: { tool: TOOL; toolCallId: string; output: InferUIMessageTools<UI_MESSAGE>[TOOL]['output'] }
80
| { state: 'output-error'; tool: TOOL; toolCallId: string; errorText: string }
81
) => Promise<void>;
82
status: 'submitted' | 'streaming' | 'ready' | 'error';
83
error: Error | undefined;
84
clearError: () => void;
85
}
86
```
87
88
[Complete useChat documentation](./use-chat.md)
89
90
### useCompletion
91
92
```typescript { .api }
93
function useCompletion(
94
options?: UseCompletionOptions & { experimental_throttle?: number }
95
): UseCompletionHelpers;
96
97
interface UseCompletionHelpers {
98
completion: string;
99
complete: (prompt: string, options?: CompletionRequestOptions) => Promise<string | null | undefined>;
100
error: undefined | Error;
101
stop: () => void;
102
setCompletion: (completion: string) => void;
103
input: string;
104
setInput: React.Dispatch<React.SetStateAction<string>>;
105
handleInputChange: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
106
handleSubmit: (event?: { preventDefault?: () => void }) => void;
107
isLoading: boolean;
108
}
109
```
110
111
[Complete useCompletion documentation](./use-completion.md)
112
113
### experimental_useObject
114
115
```typescript { .api }
116
function experimental_useObject<
117
SCHEMA extends ZodType | Schema,
118
RESULT = InferSchema<SCHEMA>,
119
INPUT = any
120
>(
121
options: Experimental_UseObjectOptions<SCHEMA, RESULT>
122
): Experimental_UseObjectHelpers<RESULT, INPUT>;
123
124
interface Experimental_UseObjectHelpers<RESULT, INPUT> {
125
submit: (input: INPUT) => void;
126
object: DeepPartial<RESULT> | undefined;
127
error: Error | undefined;
128
isLoading: boolean;
129
stop: () => void;
130
clear: () => void;
131
}
132
```
133
134
[Complete useObject documentation](./use-object.md)
135
136
## Essential Types
137
138
### UIMessage
139
140
Core message type used throughout the package.
141
142
```typescript { .api }
143
interface UIMessage<METADATA = unknown, DATA_PARTS extends UIDataTypes = UIDataTypes, TOOLS extends UITools = UITools> {
144
id: string;
145
role: 'system' | 'user' | 'assistant';
146
metadata?: METADATA;
147
parts: Array<UIMessagePart<DATA_PARTS, TOOLS>>;
148
}
149
150
// Common part types
151
type UIMessagePart<DATA_TYPES, TOOLS> =
152
| TextUIPart // { type: 'text'; text: string; state?: 'streaming' | 'done' }
153
| ReasoningUIPart // { type: 'reasoning'; text: string; state?: 'streaming' | 'done' }
154
| ToolUIPart<TOOLS>
155
| FileUIPart // { type: 'file'; mediaType: string; url: string; filename?: string }
156
| DataUIPart<DATA_TYPES>
157
| DynamicToolUIPart | SourceUrlUIPart | SourceDocumentUIPart | StepStartUIPart;
158
159
interface ToolUIPart<TOOLS extends UITools = UITools> {
160
type: `tool-${string}`;
161
toolCallId: string;
162
state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error';
163
input?: unknown;
164
output?: unknown;
165
errorText?: string;
166
providerExecuted?: boolean;
167
}
168
```
169
170
### CreateUIMessage
171
172
Type for creating new messages without requiring all fields.
173
174
```typescript { .api }
175
type CreateUIMessage<UI_MESSAGE extends UIMessage> = Omit<UI_MESSAGE, 'id' | 'role'> & {
176
id?: UI_MESSAGE['id'];
177
role?: UI_MESSAGE['role'];
178
};
179
```
180
181
### ChatInit & Options
182
183
```typescript { .api }
184
interface ChatInit<UI_MESSAGE extends UIMessage> {
185
id?: string;
186
messages?: UI_MESSAGE[];
187
transport?: ChatTransport<UI_MESSAGE>;
188
onError?: (error: Error) => void;
189
onToolCall?: (options: { toolCall: InferUIMessageToolCall<UI_MESSAGE> }) => void | PromiseLike<void>;
190
onFinish?: (options: { message: UI_MESSAGE; messages: UI_MESSAGE[]; isAbort: boolean; isDisconnect: boolean; isError: boolean }) => void;
191
sendAutomaticallyWhen?: (options: { messages: UI_MESSAGE[] }) => boolean | PromiseLike<boolean>;
192
}
193
194
interface ChatRequestOptions {
195
headers?: Record<string, string> | Headers;
196
body?: object;
197
metadata?: unknown;
198
}
199
200
interface UseCompletionOptions {
201
api?: string;
202
id?: string;
203
initialInput?: string;
204
initialCompletion?: string;
205
onFinish?: (prompt: string, completion: string) => void;
206
onError?: (error: Error) => void;
207
credentials?: RequestCredentials;
208
headers?: Record<string, string> | Headers;
209
body?: object;
210
streamProtocol?: 'data' | 'text';
211
fetch?: FetchFunction;
212
}
213
```
214
215
### Utility Types
216
217
```typescript { .api }
218
// Deeply partial type for streaming objects
219
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
220
221
// Inference helpers
222
type InferUIMessageMetadata<T extends UIMessage> = T extends UIMessage<infer METADATA, any, any> ? METADATA : unknown;
223
type InferUIMessageTools<T extends UIMessage> = T extends UIMessage<any, any, infer TOOLS> ? TOOLS : UITools;
224
type InferUIMessageToolCall<UI_MESSAGE extends UIMessage> = { toolName: string; args: unknown; toolCallId: string };
225
226
type UITools = Record<string, { input: unknown; output: unknown }>;
227
type UIDataTypes = Record<string, unknown>;
228
```
229
230
## Common Patterns
231
232
### Error Boundary Wrapper
233
234
Production-ready error handling for AI components.
235
236
```typescript
237
import { Component, ReactNode } from 'react';
238
239
interface Props { children: ReactNode; fallback?: ReactNode }
240
interface State { error: Error | null }
241
242
class AIErrorBoundary extends Component<Props, State> {
243
state: State = { error: null };
244
245
static getDerivedStateFromError(error: Error): State {
246
return { error };
247
}
248
249
componentDidCatch(error: Error, errorInfo: any) {
250
console.error('AI Component Error:', error, errorInfo);
251
// Log to error tracking service
252
}
253
254
render() {
255
if (this.state.error) {
256
return this.props.fallback || (
257
<div role="alert">
258
<p>Something went wrong with the AI component.</p>
259
<button onClick={() => this.setState({ error: null })}>Try again</button>
260
</div>
261
);
262
}
263
return this.props.children;
264
}
265
}
266
267
// Usage
268
<AIErrorBoundary>
269
<ChatComponent />
270
</AIErrorBoundary>
271
```
272
273
### Retry Logic
274
275
Implement exponential backoff for failed requests.
276
277
```typescript
278
function useRetry() {
279
const retry = async <T,>(
280
fn: () => Promise<T>,
281
maxAttempts = 3,
282
delay = 1000
283
): Promise<T> => {
284
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
285
try {
286
return await fn();
287
} catch (error) {
288
if (attempt === maxAttempts) throw error;
289
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
290
}
291
}
292
throw new Error('Max retries exceeded');
293
};
294
return { retry };
295
}
296
297
// Usage with useChat
298
const { sendMessage } = useChat({ onError: (error) => console.error(error) });
299
const { retry } = useRetry();
300
301
const handleSendWithRetry = () =>
302
retry(() => sendMessage({ text: 'Hello!' }));
303
```
304
305
### Optimistic Updates
306
307
Update UI immediately before server response.
308
309
```typescript
310
function OptimisticChat() {
311
const { messages, setMessages, sendMessage, status } = useChat();
312
313
const handleOptimisticSend = async (text: string) => {
314
const optimisticMsg: UIMessage = {
315
id: `temp-${Date.now()}`,
316
role: 'user',
317
parts: [{ type: 'text', text }],
318
};
319
320
setMessages(prev => [...prev, optimisticMsg]);
321
322
try {
323
await sendMessage({ text });
324
} catch (error) {
325
// Rollback on error
326
setMessages(prev => prev.filter(m => m.id !== optimisticMsg.id));
327
throw error;
328
}
329
};
330
331
return (
332
<div>
333
{messages.map(m => (
334
<div key={m.id} className={m.id.startsWith('temp-') ? 'optimistic' : ''}>
335
{m.parts.find(p => p.type === 'text')?.text}
336
</div>
337
))}
338
<button onClick={() => handleOptimisticSend('Hello')} disabled={status === 'streaming'}>
339
Send
340
</button>
341
</div>
342
);
343
}
344
```
345
346
### Shared State Pattern
347
348
Multiple components sharing the same chat state.
349
350
```typescript
351
// components/ChatMessages.tsx
352
function ChatMessages() {
353
const { messages } = useChat({ id: 'shared-chat' });
354
return <div>{messages.map(m => <div key={m.id}>{/* Render */}</div>)}</div>;
355
}
356
357
// components/ChatInput.tsx
358
function ChatInput() {
359
const { sendMessage, status } = useChat({ id: 'shared-chat' });
360
return (
361
<button onClick={() => sendMessage({ text: 'Hello' })} disabled={status === 'streaming'}>
362
Send
363
</button>
364
);
365
}
366
367
// app/page.tsx
368
function App() {
369
return (
370
<>
371
<ChatMessages />
372
<ChatInput />
373
</>
374
);
375
}
376
```
377
378
## Production Considerations
379
380
### Performance Optimization
381
382
```typescript
383
// Throttle updates for better performance
384
const chat = useChat({
385
experimental_throttle: 100, // Update UI every 100ms max
386
});
387
388
// Memoize message rendering
389
const MemoizedMessage = React.memo(({ message }) => (
390
<div>{message.parts.find(p => p.type === 'text')?.text}</div>
391
));
392
```
393
394
### Type Safety
395
396
```typescript
397
// Define custom message types for type safety
398
interface MyMessage extends UIMessage<
399
{ userId: string }, // metadata type
400
{ 'custom-data': string }, // data parts type
401
{ getWeather: { input: { location: string }, output: { temp: number } } } // tools type
402
> {}
403
404
const chat = useChat<MyMessage>({
405
onToolCall: ({ toolCall }) => {
406
// toolCall.args is properly typed as { location: string }
407
},
408
});
409
```
410
411
### Error Handling
412
413
```typescript
414
const chat = useChat({
415
onError: (error) => {
416
// Log to error tracking
417
console.error('Chat error:', error);
418
419
// Show user-friendly message
420
if (error.message.includes('rate limit')) {
421
toast.error('Too many requests. Please wait a moment.');
422
} else {
423
toast.error('An error occurred. Please try again.');
424
}
425
},
426
});
427
```
428
429
### Loading States
430
431
```typescript
432
function ChatWithLoadingStates() {
433
const { status, messages } = useChat();
434
435
return (
436
<div>
437
{status === 'submitted' && <div>Sending message...</div>}
438
{status === 'streaming' && <div>AI is typing...</div>}
439
{status === 'error' && <div>Error occurred</div>}
440
{status === 'ready' && messages.length === 0 && <div>Start a conversation</div>}
441
</div>
442
);
443
}
444
```
445
446
## Best Practices
447
448
1. **Always handle errors**: Implement `onError` callback and show user-friendly messages
449
2. **Use error boundaries**: Wrap AI components in error boundaries for resilience
450
3. **Throttle updates**: Use `experimental_throttle` for large responses to improve performance
451
4. **Implement retry logic**: Add exponential backoff for transient failures
452
5. **Type your messages**: Define custom message types for better type safety
453
6. **Share state wisely**: Use the `id` option to share state across components
454
7. **Handle loading states**: Show appropriate UI for each status
455
8. **Optimize rendering**: Memoize message components to prevent unnecessary re-renders
456
9. **Clean up subscriptions**: Hooks handle cleanup automatically, but be aware of component lifecycle
457
10. **Test error scenarios**: Test network failures, rate limits, and validation errors
458
459
## Advanced Topics
460
461
See individual hook documentation for:
462
- Tool call handling and automatic execution ([useChat](./use-chat.md))
463
- File uploads and multimodal messages ([useChat](./use-chat.md))
464
- Streaming optimization and debouncing ([useCompletion](./use-completion.md))
465
- Schema validation and error recovery ([experimental_useObject](./use-object.md))
466
- Custom transports and fetch implementations (all hooks)
467