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.