0
# useCompletion Hook
1
2
Single-turn text completion with streaming, built-in form helpers, and state management.
3
4
## API
5
6
```typescript { .api }
7
function useCompletion(
8
options?: UseCompletionOptions & { experimental_throttle?: number }
9
): UseCompletionHelpers;
10
11
interface UseCompletionHelpers {
12
completion: string;
13
complete: (prompt: string, options?: CompletionRequestOptions) => Promise<string | null | undefined>;
14
error: undefined | Error;
15
stop: () => void;
16
setCompletion: (completion: string) => void;
17
input: string;
18
setInput: React.Dispatch<React.SetStateAction<string>>;
19
handleInputChange: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
20
handleSubmit: (event?: { preventDefault?: () => void }) => void;
21
isLoading: boolean;
22
}
23
24
interface UseCompletionOptions {
25
api?: string; // Default: '/api/completion'
26
id?: string;
27
initialInput?: string;
28
initialCompletion?: string;
29
onFinish?: (prompt: string, completion: string) => void;
30
onError?: (error: Error) => void;
31
credentials?: RequestCredentials;
32
headers?: Record<string, string> | Headers;
33
body?: object;
34
streamProtocol?: 'data' | 'text';
35
fetch?: FetchFunction;
36
}
37
```
38
39
## Basic Usage
40
41
```typescript
42
import { useCompletion } from '@ai-sdk/react';
43
44
function CompletionComponent() {
45
const {
46
completion,
47
input,
48
handleInputChange,
49
handleSubmit,
50
isLoading,
51
error,
52
} = useCompletion({
53
api: '/api/completion',
54
onFinish: (prompt, completion) => console.log('Done:', completion),
55
onError: (error) => console.error('Error:', error),
56
});
57
58
return (
59
<div>
60
<form onSubmit={handleSubmit}>
61
<input
62
value={input}
63
onChange={handleInputChange}
64
placeholder="Enter your prompt..."
65
disabled={isLoading}
66
/>
67
<button type="submit" disabled={isLoading}>
68
{isLoading ? 'Generating...' : 'Generate'}
69
</button>
70
</form>
71
72
{error && <div className="error">{error.message}</div>}
73
{completion && <div className="completion">{completion}</div>}
74
</div>
75
);
76
}
77
```
78
79
## Production Patterns
80
81
### Debounced Autocomplete
82
83
```typescript
84
import { useCompletion } from '@ai-sdk/react';
85
import { useEffect, useRef } from 'react';
86
87
function DebouncedCompletion() {
88
const { completion, complete, isLoading, stop } = useCompletion({
89
api: '/api/autocomplete',
90
experimental_throttle: 50,
91
});
92
93
const debounceTimer = useRef<NodeJS.Timeout>();
94
const [input, setInput] = useState('');
95
96
useEffect(() => {
97
// Clear existing timer
98
if (debounceTimer.current) {
99
clearTimeout(debounceTimer.current);
100
}
101
102
// Don't trigger on empty input
103
if (!input.trim()) {
104
stop();
105
return;
106
}
107
108
// Debounce the completion request
109
debounceTimer.current = setTimeout(() => {
110
complete(input);
111
}, 500); // Wait 500ms after user stops typing
112
113
return () => {
114
if (debounceTimer.current) {
115
clearTimeout(debounceTimer.current);
116
}
117
};
118
}, [input]);
119
120
return (
121
<div>
122
<input
123
value={input}
124
onChange={(e) => setInput(e.target.value)}
125
placeholder="Type to autocomplete..."
126
/>
127
{isLoading && <span className="loading">Generating...</span>}
128
{completion && (
129
<div className="suggestion">
130
<span className="user-text">{input}</span>
131
<span className="completion">{completion}</span>
132
</div>
133
)}
134
</div>
135
);
136
}
137
```
138
139
### Retry with Exponential Backoff
140
141
```typescript
142
import { useCompletion } from '@ai-sdk/react';
143
import { useState } from 'react';
144
145
function ResilientCompletion() {
146
const [retryCount, setRetryCount] = useState(0);
147
const maxRetries = 3;
148
149
const { completion, complete, error, isLoading } = useCompletion({
150
api: '/api/completion',
151
onError: async (error) => {
152
console.error('Completion error:', error);
153
154
// Retry on network errors
155
if (
156
(error.message.includes('network') || error.message.includes('fetch')) &&
157
retryCount < maxRetries
158
) {
159
const delay = 1000 * Math.pow(2, retryCount); // 1s, 2s, 4s
160
console.log(`Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
161
162
await new Promise(resolve => setTimeout(resolve, delay));
163
setRetryCount(prev => prev + 1);
164
// complete will be called again by user
165
}
166
},
167
onFinish: () => {
168
setRetryCount(0); // Reset on success
169
},
170
});
171
172
const handleGenerate = async (prompt: string) => {
173
try {
174
await complete(prompt);
175
} catch (err) {
176
// Error handled in onError
177
}
178
};
179
180
return (
181
<div>
182
{error && (
183
<div className="error">
184
Error: {error.message}
185
{retryCount > 0 && <span> (Retry {retryCount}/{maxRetries})</span>}
186
<button onClick={() => handleGenerate('Retry this')}>Retry Now</button>
187
</div>
188
)}
189
190
<button onClick={() => handleGenerate('Write a poem')} disabled={isLoading}>
191
Generate
192
</button>
193
194
{isLoading && <div>Loading...</div>}
195
{completion && <p>{completion}</p>}
196
</div>
197
);
198
}
199
```
200
201
### Result Caching
202
203
```typescript
204
import { useCompletion } from '@ai-sdk/react';
205
import { useState, useEffect } from 'react';
206
207
// Simple in-memory cache
208
const completionCache = new Map<string, string>();
209
210
function CachedCompletion() {
211
const [prompt, setPrompt] = useState('');
212
const { completion, setCompletion, complete, isLoading } = useCompletion();
213
214
const handleGenerate = async () => {
215
const cacheKey = prompt.toLowerCase().trim();
216
217
// Check cache first
218
if (completionCache.has(cacheKey)) {
219
console.log('Cache hit');
220
setCompletion(completionCache.get(cacheKey)!);
221
return;
222
}
223
224
// Generate new completion
225
const result = await complete(prompt);
226
227
// Cache the result
228
if (result) {
229
completionCache.set(cacheKey, result);
230
}
231
};
232
233
return (
234
<div>
235
<input value={prompt} onChange={(e) => setPrompt(e.target.value)} />
236
<button onClick={handleGenerate} disabled={isLoading}>
237
Generate
238
</button>
239
240
{completion && (
241
<div>
242
<p>{completion}</p>
243
<button onClick={() => completionCache.delete(prompt.toLowerCase().trim())}>
244
Clear Cache
245
</button>
246
</div>
247
)}
248
</div>
249
);
250
}
251
```
252
253
### Streaming with Cancel
254
255
```typescript
256
import { useCompletion } from '@ai-sdk/react';
257
258
function StreamingCompletion() {
259
const { completion, complete, stop, isLoading, setCompletion } = useCompletion({
260
api: '/api/completion',
261
experimental_throttle: 50,
262
});
263
264
const [showCancel, setShowCancel] = useState(false);
265
266
const handleStart = async () => {
267
setShowCancel(true);
268
setCompletion(''); // Clear previous completion
269
await complete('Write a long story about space exploration');
270
setShowCancel(false);
271
};
272
273
const handleCancel = () => {
274
stop(); // Stops streaming but keeps current text
275
setShowCancel(false);
276
};
277
278
return (
279
<div>
280
<button onClick={handleStart} disabled={isLoading}>
281
Start Generation
282
</button>
283
284
{showCancel && (
285
<button onClick={handleCancel} className="cancel">
286
Cancel
287
</button>
288
)}
289
290
<div className="streaming-output">
291
{completion}
292
{isLoading && <span className="cursor">▊</span>}
293
</div>
294
295
{!isLoading && completion && (
296
<div className="stats">
297
<small>{completion.length} characters</small>
298
</div>
299
)}
300
</div>
301
);
302
}
303
```
304
305
### Custom Headers and Body
306
307
```typescript
308
import { useCompletion } from '@ai-sdk/react';
309
310
function CustomCompletion() {
311
const { completion, complete, isLoading } = useCompletion({
312
api: '/api/completion',
313
headers: {
314
'X-API-Key': process.env.NEXT_PUBLIC_API_KEY || '',
315
'X-User-ID': 'user-123',
316
},
317
body: {
318
model: 'gpt-4',
319
temperature: 0.7,
320
max_tokens: 500,
321
},
322
credentials: 'include',
323
});
324
325
const handleGenerate = async (prompt: string, options?: { temperature?: number }) => {
326
// Override options for this specific request
327
await complete(prompt, {
328
body: {
329
temperature: options?.temperature || 0.7,
330
max_tokens: 500,
331
},
332
headers: {
333
'X-Priority': 'high',
334
},
335
});
336
};
337
338
return (
339
<div>
340
<button onClick={() => handleGenerate('Be creative', { temperature: 0.9 })}>
341
Creative (temp 0.9)
342
</button>
343
<button onClick={() => handleGenerate('Be precise', { temperature: 0.3 })}>
344
Precise (temp 0.3)
345
</button>
346
347
{isLoading && <div>Generating...</div>}
348
{completion && <p>{completion}</p>}
349
</div>
350
);
351
}
352
```
353
354
### State Management with History
355
356
```typescript
357
import { useCompletion } from '@ai-sdk/react';
358
import { useState } from 'react';
359
360
interface CompletionHistory {
361
prompt: string;
362
completion: string;
363
timestamp: number;
364
}
365
366
function CompletionWithHistory() {
367
const { completion, setCompletion, complete, input, setInput, isLoading } = useCompletion();
368
const [history, setHistory] = useState<CompletionHistory[]>([]);
369
370
const handleSave = () => {
371
if (completion && input) {
372
const entry: CompletionHistory = {
373
prompt: input,
374
completion,
375
timestamp: Date.now(),
376
};
377
setHistory(prev => [entry, ...prev]);
378
setCompletion('');
379
setInput('');
380
}
381
};
382
383
const handleRestore = (entry: CompletionHistory) => {
384
setInput(entry.prompt);
385
setCompletion(entry.completion);
386
};
387
388
const handleRegenerate = async (prompt: string) => {
389
setInput(prompt);
390
await complete(prompt);
391
};
392
393
return (
394
<div>
395
<div className="editor">
396
<textarea
397
value={input}
398
onChange={(e) => setInput(e.target.value)}
399
placeholder="Enter prompt..."
400
rows={3}
401
/>
402
<button onClick={() => complete(input)} disabled={isLoading || !input}>
403
Generate
404
</button>
405
</div>
406
407
{completion && (
408
<div className="result">
409
<p>{completion}</p>
410
<button onClick={handleSave}>Save to History</button>
411
<button onClick={() => setCompletion('')}>Clear</button>
412
</div>
413
)}
414
415
<div className="history">
416
<h3>History</h3>
417
{history.map((entry, i) => (
418
<div key={i} className="history-entry">
419
<div className="prompt">{entry.prompt}</div>
420
<div className="completion">{entry.completion.slice(0, 100)}...</div>
421
<div className="actions">
422
<button onClick={() => handleRestore(entry)}>Restore</button>
423
<button onClick={() => handleRegenerate(entry.prompt)}>Regenerate</button>
424
<button onClick={() => setHistory(prev => prev.filter((_, idx) => idx !== i))}>
425
Delete
426
</button>
427
</div>
428
<small>{new Date(entry.timestamp).toLocaleString()}</small>
429
</div>
430
))}
431
</div>
432
</div>
433
);
434
}
435
```
436
437
### Shared State Across Components
438
439
```typescript
440
// components/CompletionDisplay.tsx
441
import { useCompletion } from '@ai-sdk/react';
442
443
export function CompletionDisplay() {
444
const { completion, isLoading } = useCompletion({ id: 'shared-completion' });
445
446
return (
447
<div className="display">
448
{isLoading && <div className="loading">Generating...</div>}
449
<div className="completion-text">{completion}</div>
450
</div>
451
);
452
}
453
454
// components/CompletionInput.tsx
455
export function CompletionInput() {
456
const { input, handleInputChange, handleSubmit, isLoading } = useCompletion({
457
id: 'shared-completion',
458
});
459
460
return (
461
<form onSubmit={handleSubmit}>
462
<input value={input} onChange={handleInputChange} disabled={isLoading} />
463
<button type="submit" disabled={isLoading}>
464
Generate
465
</button>
466
</form>
467
);
468
}
469
470
// components/CompletionControls.tsx
471
export function CompletionControls() {
472
const { stop, setCompletion, isLoading } = useCompletion({ id: 'shared-completion' });
473
474
return (
475
<div className="controls">
476
{isLoading && <button onClick={stop}>Stop</button>}
477
<button onClick={() => setCompletion('')}>Clear</button>
478
</div>
479
);
480
}
481
482
// app/page.tsx
483
export default function App() {
484
return (
485
<div>
486
<CompletionInput />
487
<CompletionControls />
488
<CompletionDisplay />
489
</div>
490
);
491
}
492
```
493
494
### Custom Fetch with Middleware
495
496
```typescript
497
import { useCompletion } from '@ai-sdk/react';
498
499
function CompletionWithMiddleware() {
500
const { completion, complete } = useCompletion({
501
api: '/api/completion',
502
fetch: async (url, options) => {
503
// Add logging
504
console.log('Fetching:', url, options);
505
506
// Add authentication
507
const token = localStorage.getItem('auth_token');
508
509
// Add request timing
510
const startTime = Date.now();
511
512
try {
513
const response = await fetch(url, {
514
...options,
515
headers: {
516
...options?.headers,
517
Authorization: token ? `Bearer ${token}` : '',
518
},
519
});
520
521
// Log response time
522
console.log(`Request took ${Date.now() - startTime}ms`);
523
524
// Handle errors
525
if (!response.ok) {
526
const errorData = await response.json().catch(() => null);
527
throw new Error(errorData?.message || `HTTP ${response.status}`);
528
}
529
530
return response;
531
} catch (error) {
532
// Log to error tracking service
533
console.error('Fetch error:', error);
534
throw error;
535
}
536
},
537
});
538
539
return (
540
<div>
541
<button onClick={() => complete('Generate text')}>Generate</button>
542
<p>{completion}</p>
543
</div>
544
);
545
}
546
```
547
548
## Best Practices
549
550
1. **Debounce user input**: Use debouncing for autocomplete or real-time suggestions
551
2. **Implement retry logic**: Add exponential backoff for transient failures
552
3. **Cache results**: Cache completions to reduce API calls and costs
553
4. **Handle cancellation**: Allow users to stop long-running completions
554
5. **Show progress**: Display loading states and streaming indicators
555
6. **Error recovery**: Provide clear error messages and retry options
556
7. **Validate input**: Check input before sending to avoid unnecessary API calls
557
8. **Optimize throttling**: Use `experimental_throttle` for smooth streaming
558
9. **State management**: Save completion history for better UX
559
10. **Monitor performance**: Track response times and error rates
560
561
## Streaming Protocols
562
563
```typescript
564
// Data protocol (default) - structured streaming with metadata
565
const dataProtocol = useCompletion({
566
api: '/api/completion',
567
streamProtocol: 'data', // Default
568
});
569
570
// Text protocol - raw text streaming
571
const textProtocol = useCompletion({
572
api: '/api/text-completion',
573
streamProtocol: 'text',
574
});
575
```
576
577
## Performance Tips
578
579
```typescript
580
// 1. Throttle updates for better performance
581
const { completion } = useCompletion({
582
experimental_throttle: 100, // Update UI every 100ms max
583
});
584
585
// 2. Memoize rendered output
586
const MemoizedCompletion = React.memo(({ text }: { text: string }) => (
587
<div>{text}</div>
588
));
589
590
// 3. Use TextDecoder for efficient text processing (handled internally)
591
592
// 4. Implement request deduplication
593
let requestInFlight = false;
594
const handleGenerate = async () => {
595
if (requestInFlight) return;
596
requestInFlight = true;
597
try {
598
await complete('prompt');
599
} finally {
600
requestInFlight = false;
601
}
602
};
603
```
604