or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

use-completion.mddocs/

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