or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assistants.mdauth.mdclient.mdcrons.mdindex.mdreact.mdruns.mdstore.mdthreads.md

react.mddocs/

0

# React Integration

1

2

The React integration provides comprehensive hooks and UI components for building interactive LangGraph applications. It includes streaming hooks for real-time execution monitoring, UI component loading for dynamic interfaces, and complete React state management for LangGraph interactions.

3

4

## Core Functionality

5

6

The React integration supports:

7

8

- **Streaming Hooks**: Real-time streaming of LangGraph runs with React state management

9

- **UI Components**: Dynamic loading of external UI components with context

10

- **Message Management**: UI message system with reducers and type safety

11

- **Server Utilities**: Server-side helpers for type-safe UI component rendering

12

- **Branch Management**: Handle execution branches and history in React applications

13

- **Error Handling**: Comprehensive error states and loading management

14

15

## useStream Hook

16

17

The main React hook for streaming LangGraph runs with comprehensive state management.

18

19

```typescript { .api }

20

function useStream<StateType = any, Bag = any>(

21

options: UseStreamOptions<StateType, Bag>

22

): UseStream<StateType, Bag>;

23

24

interface UseStreamOptions<StateType, Bag> {

25

/** LangGraph client instance */

26

client: Client;

27

/** Assistant ID to execute */

28

assistantId: string;

29

/** Thread ID (optional, will create new if not provided) */

30

threadId?: string;

31

/** Stream modes to enable */

32

streamMode?: StreamMode | StreamMode[];

33

/** Enable subgraph streaming */

34

streamSubgraphs?: boolean;

35

/** Initial input data */

36

input?: Record<string, any>;

37

/** Run configuration */

38

config?: Config;

39

/** Run metadata */

40

metadata?: Metadata;

41

/** Custom event handling */

42

onEvent?: (event: StreamEvent) => void;

43

/** Error handler */

44

onError?: (error: Error) => void;

45

/** Additional bag data for context */

46

bag?: Bag;

47

}

48

49

interface UseStream<StateType, Bag> {

50

/** Current state values from the stream */

51

values: StateType | null;

52

53

/** Current error state */

54

error: Error | null;

55

56

/** Loading state indicator */

57

isLoading: boolean;

58

59

/** Function to stop the current stream */

60

stop: () => void;

61

62

/** Function to submit input and start streaming */

63

submit: (input: Record<string, any>, options?: SubmitOptions) => Promise<void>;

64

65

/** Current execution branch information */

66

branch: Branch | null;

67

68

/** Function to set/switch execution branch */

69

setBranch: (branch: Branch) => void;

70

71

/** Complete execution history */

72

history: HistoryItem<StateType>[];

73

74

/** Experimental branch tree structure */

75

experimental_branchTree: BranchTree | null;

76

77

/** Function to interrupt current execution */

78

interrupt: (options?: InterruptOptions) => Promise<void>;

79

80

/** Message history from the stream */

81

messages: Message[];

82

83

/** Function to get metadata for messages */

84

getMessagesMetadata: (messageIds: string[]) => MessageMetadata<StateType>[];

85

86

/** LangGraph client instance */

87

client: Client;

88

89

/** Current assistant ID */

90

assistantId: string;

91

92

/** Function to join an existing stream */

93

joinStream: (runId: string, options?: JoinStreamOptions) => Promise<void>;

94

95

/** Additional context bag data */

96

bag: Bag;

97

}

98

```

99

100

## LoadExternalComponent

101

102

React component for loading external UI components dynamically with stream context.

103

104

```typescript { .api }

105

function LoadExternalComponent(props: LoadExternalComponentProps): React.ReactElement;

106

107

interface LoadExternalComponentProps {

108

/** Stream object from useStream hook */

109

stream: UseStream<any, any>;

110

/** UI message to render */

111

message: UIMessage<any, any>;

112

/** Optional namespace for component resolution */

113

namespace?: string;

114

/** Optional metadata */

115

meta?: any;

116

/** Fallback component if loading fails */

117

fallback?: React.ComponentType<any>;

118

/** Component registry for custom components */

119

components?: Record<string, React.ComponentType<any>>;

120

/** Inline styles */

121

style?: React.CSSProperties;

122

/** CSS class name */

123

className?: string;

124

}

125

126

function useStreamContext<StateType, Bag>(): UseStreamContext<StateType, Bag>;

127

128

interface UseStreamContext<StateType, Bag> {

129

/** Current stream instance */

130

stream: UseStream<StateType, Bag>;

131

/** Current message being rendered */

132

message: UIMessage<any, any>;

133

/** Namespace context */

134

namespace?: string;

135

/** Additional metadata */

136

meta?: any;

137

}

138

```

139

140

## UI Message System

141

142

Type-safe UI message management with reducers and utilities.

143

144

```typescript { .api }

145

interface UIMessage<TName extends string = string, TProps = any> {

146

/** Message type identifier */

147

type: TName;

148

/** Message properties */

149

props: TProps;

150

/** Unique message ID */

151

id: string;

152

/** Message timestamp */

153

timestamp: string;

154

/** Optional message metadata */

155

metadata?: Record<string, any>;

156

}

157

158

interface RemoveUIMessage {

159

/** Action type for removal */

160

type: "remove";

161

/** ID of message to remove */

162

id: string;

163

}

164

165

// Type guard functions

166

function isUIMessage(message: unknown): message is UIMessage;

167

function isRemoveUIMessage(message: unknown): message is RemoveUIMessage;

168

169

// UI message reducer for state management

170

function uiMessageReducer(

171

state: UIMessage[],

172

action: UIMessage | RemoveUIMessage

173

): UIMessage[];

174

175

/**

176

* Experimental function for loading shared UI components

177

* @param options - Configuration options for shared component loading

178

* @returns Promise resolving to loaded shared component

179

*/

180

function experimental_loadShare(options: LoadShareOptions): Promise<any>;

181

182

interface LoadShareOptions {

183

/** Component identifier to load */

184

componentId: string;

185

/** Optional namespace for component resolution */

186

namespace?: string;

187

/** Component loading options */

188

options?: Record<string, unknown>;

189

}

190

```

191

192

## Server Utilities

193

194

Server-side helpers for type-safe UI component rendering.

195

196

```typescript { .api }

197

function typedUi<Decl extends Record<string, React.ElementType>>(

198

config: TypedUiConfig<Decl>,

199

options?: TypedUiOptions

200

): TypedUiInstance<Decl>;

201

202

interface TypedUiConfig<Decl extends Record<string, React.ElementType>> {

203

/** Component declarations mapping names to types */

204

components: Decl;

205

/** Default namespace for components */

206

namespace?: string;

207

}

208

209

interface TypedUiOptions {

210

/** Enable development mode features */

211

dev?: boolean;

212

/** Custom component resolver */

213

resolver?: (name: string) => React.ElementType | undefined;

214

}

215

216

interface TypedUiInstance<Decl extends Record<string, React.ElementType>> {

217

/** Push a new UI message */

218

push<K extends keyof Decl>(

219

name: K,

220

props: React.ComponentProps<Decl[K]>

221

): UIMessage<K, React.ComponentProps<Decl[K]>>;

222

223

/** Delete a UI message by ID */

224

delete(id: string): RemoveUIMessage;

225

226

/** Current UI message items */

227

items: UIMessage[];

228

}

229

```

230

231

## Supporting Types

232

233

### Stream and Branch Management

234

235

```typescript { .api }

236

interface Branch {

237

/** Branch identifier */

238

id: string;

239

/** Branch name */

240

name?: string;

241

/** Parent branch ID */

242

parentId?: string;

243

/** Branch checkpoint */

244

checkpoint: string;

245

/** Branch metadata */

246

metadata?: Record<string, any>;

247

}

248

249

interface BranchTree {

250

/** Root branch */

251

root: Branch;

252

/** All branches in tree */

253

branches: Record<string, Branch>;

254

/** Current active branch */

255

current: string;

256

}

257

258

interface HistoryItem<StateType> {

259

/** History item ID */

260

id: string;

261

/** Associated checkpoint */

262

checkpoint: string;

263

/** State values at this point */

264

values: StateType;

265

/** Timestamp */

266

timestamp: string;

267

/** Associated messages */

268

messages: Message[];

269

/** Metadata */

270

metadata?: Record<string, any>;

271

}

272

273

interface MessageMetadata<StateType> {

274

/** Message ID */

275

messageId: string;

276

/** Associated branch */

277

branch: Branch;

278

/** State at message time */

279

state: StateType;

280

/** Message timestamp */

281

timestamp: string;

282

}

283

```

284

285

### Submit and Interrupt Options

286

287

```typescript { .api }

288

interface SubmitOptions {

289

/** Override stream mode */

290

streamMode?: StreamMode | StreamMode[];

291

/** Override configuration */

292

config?: Config;

293

/** Additional metadata */

294

metadata?: Metadata;

295

/** Reset stream state before submit */

296

reset?: boolean;

297

}

298

299

interface InterruptOptions {

300

/** Interrupt after specific nodes */

301

after?: string[];

302

/** Interrupt before specific nodes */

303

before?: string[];

304

/** Interrupt action */

305

action?: "interrupt" | "rollback";

306

}

307

308

interface JoinStreamOptions {

309

/** Stream modes to join */

310

streamMode?: StreamMode | StreamMode[];

311

/** Enable subgraph streaming */

312

streamSubgraphs?: boolean;

313

}

314

```

315

316

## Usage Examples

317

318

### Basic Streaming Hook

319

320

```typescript

321

import React, { useState } from 'react';

322

import { useStream } from '@langchain/langgraph-sdk/react';

323

import { Client } from '@langchain/langgraph-sdk';

324

325

const client = new Client({

326

apiUrl: 'https://api.langgraph.example.com',

327

apiKey: 'your-api-key'

328

});

329

330

interface AppState {

331

messages: Array<{ role: string; content: string }>;

332

context: Record<string, any>;

333

}

334

335

export function ChatInterface() {

336

const [input, setInput] = useState('');

337

338

const stream = useStream<AppState>({

339

client,

340

assistantId: 'assistant_123',

341

streamMode: ['values', 'messages'],

342

onError: (error) => {

343

console.error('Stream error:', error);

344

}

345

});

346

347

const handleSubmit = async () => {

348

if (!input.trim()) return;

349

350

await stream.submit({

351

message: input,

352

timestamp: new Date().toISOString()

353

});

354

355

setInput('');

356

};

357

358

return (

359

<div className="chat-interface">

360

<div className="messages">

361

{stream.messages.map((message, index) => (

362

<div key={index} className={`message ${message.type}`}>

363

<div className="content">{message.content}</div>

364

<div className="timestamp">

365

{new Date(message.timestamp).toLocaleTimeString()}

366

</div>

367

</div>

368

))}

369

</div>

370

371

{stream.isLoading && (

372

<div className="loading">Processing...</div>

373

)}

374

375

{stream.error && (

376

<div className="error">

377

Error: {stream.error.message}

378

<button onClick={() => window.location.reload()}>

379

Retry

380

</button>

381

</div>

382

)}

383

384

<div className="input-area">

385

<input

386

type="text"

387

value={input}

388

onChange={(e) => setInput(e.target.value)}

389

onKeyPress={(e) => e.key === 'Enter' && handleSubmit()}

390

placeholder="Type your message..."

391

disabled={stream.isLoading}

392

/>

393

<button

394

onClick={handleSubmit}

395

disabled={stream.isLoading || !input.trim()}

396

>

397

Send

398

</button>

399

{stream.isLoading && (

400

<button onClick={stream.stop}>Stop</button>

401

)}

402

</div>

403

404

{stream.values && (

405

<div className="debug-info">

406

<h3>Current State:</h3>

407

<pre>{JSON.stringify(stream.values, null, 2)}</pre>

408

</div>

409

)}

410

</div>

411

);

412

}

413

```

414

415

### Advanced Stream Management with Branches

416

417

```typescript

418

import React, { useState, useEffect } from 'react';

419

import { useStream } from '@langchain/langgraph-sdk/react';

420

421

export function AdvancedStreamManager() {

422

const [selectedBranch, setSelectedBranch] = useState<string | null>(null);

423

424

const stream = useStream({

425

client,

426

assistantId: 'assistant_advanced',

427

streamMode: ['values', 'updates', 'messages', 'debug'],

428

streamSubgraphs: true,

429

onEvent: (event) => {

430

console.log('Stream event:', event);

431

}

432

});

433

434

// Handle branch switching

435

useEffect(() => {

436

if (selectedBranch && stream.setBranch) {

437

const branch = stream.experimental_branchTree?.branches[selectedBranch];

438

if (branch) {

439

stream.setBranch(branch);

440

}

441

}

442

}, [selectedBranch, stream]);

443

444

const handleInterrupt = async () => {

445

await stream.interrupt({

446

after: ['node_1', 'node_2'],

447

action: 'interrupt'

448

});

449

};

450

451

const renderBranchTree = () => {

452

if (!stream.experimental_branchTree) return null;

453

454

const { branches, current } = stream.experimental_branchTree;

455

456

return (

457

<div className="branch-tree">

458

<h3>Execution Branches</h3>

459

{Object.entries(branches).map(([id, branch]) => (

460

<div

461

key={id}

462

className={`branch ${id === current ? 'active' : ''}`}

463

onClick={() => setSelectedBranch(id)}

464

>

465

<div className="branch-name">

466

{branch.name || `Branch ${id.slice(0, 8)}`}

467

</div>

468

<div className="branch-checkpoint">

469

Checkpoint: {branch.checkpoint.slice(0, 8)}...

470

</div>

471

</div>

472

))}

473

</div>

474

);

475

};

476

477

const renderHistory = () => (

478

<div className="history">

479

<h3>Execution History</h3>

480

{stream.history.map((item, index) => (

481

<div key={item.id} className="history-item">

482

<div className="history-header">

483

<span className="checkpoint">

484

{item.checkpoint.slice(0, 8)}...

485

</span>

486

<span className="timestamp">

487

{new Date(item.timestamp).toLocaleString()}

488

</span>

489

</div>

490

<div className="history-content">

491

<pre>{JSON.stringify(item.values, null, 2)}</pre>

492

</div>

493

</div>

494

))}

495

</div>

496

);

497

498

return (

499

<div className="advanced-stream-manager">

500

<div className="controls">

501

<button

502

onClick={() => stream.submit({ action: 'analyze' })}

503

disabled={stream.isLoading}

504

>

505

Start Analysis

506

</button>

507

<button

508

onClick={handleInterrupt}

509

disabled={!stream.isLoading}

510

>

511

Interrupt

512

</button>

513

<button onClick={stream.stop}>Stop</button>

514

</div>

515

516

<div className="content">

517

<div className="left-panel">

518

{renderBranchTree()}

519

</div>

520

521

<div className="main-panel">

522

<div className="current-state">

523

<h3>Current State</h3>

524

{stream.values ? (

525

<pre>{JSON.stringify(stream.values, null, 2)}</pre>

526

) : (

527

<p>No state data</p>

528

)}

529

</div>

530

531

{stream.messages.length > 0 && (

532

<div className="messages">

533

<h3>Messages</h3>

534

{stream.messages.map((message, index) => (

535

<div key={index} className="message">

536

<strong>{message.type}:</strong> {message.content}

537

</div>

538

))}

539

</div>

540

)}

541

</div>

542

543

<div className="right-panel">

544

{renderHistory()}

545

</div>

546

</div>

547

</div>

548

);

549

}

550

```

551

552

### Dynamic UI Components

553

554

```typescript

555

import React from 'react';

556

import { LoadExternalComponent, useStream } from '@langchain/langgraph-sdk/react';

557

558

// Custom UI components

559

const ChartComponent = ({ data, title }: { data: number[]; title: string }) => (

560

<div className="chart">

561

<h3>{title}</h3>

562

<div className="bars">

563

{data.map((value, index) => (

564

<div

565

key={index}

566

className="bar"

567

style={{ height: `${value}px` }}

568

/>

569

))}

570

</div>

571

</div>

572

);

573

574

const FormComponent = ({ fields, onSubmit }: {

575

fields: Array<{ name: string; type: string; label: string }>;

576

onSubmit: (data: Record<string, any>) => void;

577

}) => {

578

const [formData, setFormData] = useState({});

579

580

return (

581

<form onSubmit={(e) => { e.preventDefault(); onSubmit(formData); }}>

582

{fields.map(field => (

583

<div key={field.name} className="field">

584

<label>{field.label}</label>

585

<input

586

type={field.type}

587

onChange={(e) => setFormData({

588

...formData,

589

[field.name]: e.target.value

590

})}

591

/>

592

</div>

593

))}

594

<button type="submit">Submit</button>

595

</form>

596

);

597

};

598

599

export function DynamicUIDemo() {

600

const stream = useStream({

601

client,

602

assistantId: 'ui_assistant',

603

streamMode: ['values', 'custom'],

604

onEvent: (event) => {

605

// Handle custom UI events

606

if (event.event === 'custom' && isUIMessage(event.data)) {

607

console.log('UI message received:', event.data);

608

}

609

}

610

});

611

612

// Component registry

613

const components = {

614

chart: ChartComponent,

615

form: FormComponent

616

};

617

618

const renderUIMessages = () => {

619

// Extract UI messages from stream

620

const uiMessages = stream.messages.filter(isUIMessage);

621

622

return uiMessages.map(message => (

623

<LoadExternalComponent

624

key={message.id}

625

stream={stream}

626

message={message}

627

components={components}

628

fallback={() => <div>Component not found: {message.type}</div>}

629

className="dynamic-component"

630

/>

631

));

632

};

633

634

return (

635

<div className="dynamic-ui-demo">

636

<div className="controls">

637

<button onClick={() => stream.submit({

638

action: 'generate_chart',

639

data: [10, 20, 30, 15, 25]

640

})}>

641

Generate Chart

642

</button>

643

<button onClick={() => stream.submit({

644

action: 'create_form',

645

fields: [

646

{ name: 'name', type: 'text', label: 'Name' },

647

{ name: 'email', type: 'email', label: 'Email' }

648

]

649

})}>

650

Create Form

651

</button>

652

</div>

653

654

<div className="ui-components">

655

{renderUIMessages()}

656

</div>

657

658

{stream.error && (

659

<div className="error">Error: {stream.error.message}</div>

660

)}

661

</div>

662

);

663

}

664

```

665

666

### UI Message Management with Reducer

667

668

```typescript

669

import React, { useReducer, useEffect } from 'react';

670

import { uiMessageReducer, UIMessage, RemoveUIMessage } from '@langchain/langgraph-sdk/react-ui';

671

672

export function UIMessageManager() {

673

const [uiMessages, dispatchUIMessage] = useReducer(uiMessageReducer, []);

674

675

const stream = useStream({

676

client,

677

assistantId: 'message_assistant',

678

onEvent: (event) => {

679

if (event.event === 'custom') {

680

const data = event.data;

681

682

if (isUIMessage(data)) {

683

dispatchUIMessage(data);

684

} else if (isRemoveUIMessage(data)) {

685

dispatchUIMessage(data);

686

}

687

}

688

}

689

});

690

691

// Simulate adding messages

692

const addMessage = (type: string, props: any) => {

693

const message: UIMessage = {

694

type,

695

props,

696

id: `msg_${Date.now()}`,

697

timestamp: new Date().toISOString()

698

};

699

dispatchUIMessage(message);

700

};

701

702

const removeMessage = (id: string) => {

703

const removeAction: RemoveUIMessage = {

704

type: 'remove',

705

id

706

};

707

dispatchUIMessage(removeAction);

708

};

709

710

return (

711

<div className="ui-message-manager">

712

<div className="controls">

713

<button onClick={() => addMessage('notification', {

714

title: 'Success',

715

message: 'Operation completed',

716

type: 'success'

717

})}>

718

Add Success Notification

719

</button>

720

721

<button onClick={() => addMessage('progress', {

722

value: 50,

723

label: 'Processing...'

724

})}>

725

Add Progress Bar

726

</button>

727

728

<button onClick={() => {

729

if (uiMessages.length > 0) {

730

removeMessage(uiMessages[0].id);

731

}

732

}}>

733

Remove First Message

734

</button>

735

</div>

736

737

<div className="messages">

738

{uiMessages.map(message => (

739

<div key={message.id} className="ui-message">

740

<div className="message-header">

741

<span className="type">{message.type}</span>

742

<button onClick={() => removeMessage(message.id)}>×</button>

743

</div>

744

<div className="message-content">

745

<pre>{JSON.stringify(message.props, null, 2)}</pre>

746

</div>

747

</div>

748

))}

749

</div>

750

</div>

751

);

752

}

753

```

754

755

### Server-Side UI Utilities

756

757

```typescript

758

// Server-side component definition

759

import { typedUi } from '@langchain/langgraph-sdk/react-ui/server';

760

761

// Define component types

762

interface ButtonProps {

763

label: string;

764

onClick?: () => void;

765

variant?: 'primary' | 'secondary';

766

}

767

768

interface CardProps {

769

title: string;

770

content: string;

771

actions?: ButtonProps[];

772

}

773

774

// Create typed UI instance

775

const ui = typedUi({

776

components: {

777

button: {} as React.ComponentType<ButtonProps>,

778

card: {} as React.ComponentType<CardProps>

779

},

780

namespace: 'app'

781

});

782

783

// Usage in LangGraph node

784

function createUIMessage() {

785

// Type-safe UI message creation

786

const buttonMessage = ui.push('button', {

787

label: 'Click me',

788

variant: 'primary',

789

onClick: () => console.log('Button clicked')

790

});

791

792

const cardMessage = ui.push('card', {

793

title: 'Information Card',

794

content: 'This is some important information',

795

actions: [

796

{ label: 'Accept', variant: 'primary' },

797

{ label: 'Decline', variant: 'secondary' }

798

]

799

});

800

801

return {

802

ui_messages: [buttonMessage, cardMessage]

803

};

804

}

805

806

// Delete UI messages

807

function cleanupUI() {

808

const deleteMessage = ui.delete('message_id_123');

809

return { ui_messages: [deleteMessage] };

810

}

811

```

812

813

### Error Handling and Recovery

814

815

```typescript

816

export function RobustStreamingApp() {

817

const [retryCount, setRetryCount] = useState(0);

818

const maxRetries = 3;

819

820

const stream = useStream({

821

client,

822

assistantId: 'reliable_assistant',

823

onError: (error) => {

824

console.error('Stream error:', error);

825

826

// Automatic retry with backoff

827

if (retryCount < maxRetries) {

828

const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff

829

setTimeout(() => {

830

setRetryCount(prev => prev + 1);

831

stream.submit({ retry: true, attempt: retryCount + 1 });

832

}, delay);

833

}

834

}

835

});

836

837

const handleManualRetry = () => {

838

setRetryCount(0);

839

stream.submit({ manual_retry: true });

840

};

841

842

return (

843

<div className="robust-streaming-app">

844

{stream.error && (

845

<div className="error-banner">

846

<div className="error-message">

847

{stream.error.message}

848

</div>

849

<div className="error-actions">

850

{retryCount < maxRetries ? (

851

<div>Retrying... (Attempt {retryCount + 1}/{maxRetries})</div>

852

) : (

853

<button onClick={handleManualRetry}>

854

Retry Manually

855

</button>

856

)}

857

</div>

858

</div>

859

)}

860

861

<div className="stream-content">

862

{stream.values && (

863

<pre>{JSON.stringify(stream.values, null, 2)}</pre>

864

)}

865

</div>

866

867

<div className="stream-status">

868

<div>Loading: {stream.isLoading ? 'Yes' : 'No'}</div>

869

<div>Error: {stream.error ? 'Yes' : 'No'}</div>

870

<div>Retry Count: {retryCount}</div>

871

</div>

872

</div>

873

);

874

}

875

```

876

877

The React integration provides comprehensive tools for building interactive LangGraph applications with real-time streaming, dynamic UI components, and robust state management, making it easy to create responsive and feature-rich user interfaces.