or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mduse-chat.mduse-completion.mduse-object.md

index.mddocs/

0

# 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