TypeScript/JavaScript SDK for interacting with the LangGraph REST API server
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.
The React integration supports:
The main React hook for streaming LangGraph runs with comprehensive state management.
function useStream<StateType = any, Bag = any>(
options: UseStreamOptions<StateType, Bag>
): UseStream<StateType, Bag>;
interface UseStreamOptions<StateType, Bag> {
/** LangGraph client instance */
client: Client;
/** Assistant ID to execute */
assistantId: string;
/** Thread ID (optional, will create new if not provided) */
threadId?: string;
/** Stream modes to enable */
streamMode?: StreamMode | StreamMode[];
/** Enable subgraph streaming */
streamSubgraphs?: boolean;
/** Initial input data */
input?: Record<string, any>;
/** Run configuration */
config?: Config;
/** Run metadata */
metadata?: Metadata;
/** Custom event handling */
onEvent?: (event: StreamEvent) => void;
/** Error handler */
onError?: (error: Error) => void;
/** Additional bag data for context */
bag?: Bag;
}
interface UseStream<StateType, Bag> {
/** Current state values from the stream */
values: StateType | null;
/** Current error state */
error: Error | null;
/** Loading state indicator */
isLoading: boolean;
/** Function to stop the current stream */
stop: () => void;
/** Function to submit input and start streaming */
submit: (input: Record<string, any>, options?: SubmitOptions) => Promise<void>;
/** Current execution branch information */
branch: Branch | null;
/** Function to set/switch execution branch */
setBranch: (branch: Branch) => void;
/** Complete execution history */
history: HistoryItem<StateType>[];
/** Experimental branch tree structure */
experimental_branchTree: BranchTree | null;
/** Function to interrupt current execution */
interrupt: (options?: InterruptOptions) => Promise<void>;
/** Message history from the stream */
messages: Message[];
/** Function to get metadata for messages */
getMessagesMetadata: (messageIds: string[]) => MessageMetadata<StateType>[];
/** LangGraph client instance */
client: Client;
/** Current assistant ID */
assistantId: string;
/** Function to join an existing stream */
joinStream: (runId: string, options?: JoinStreamOptions) => Promise<void>;
/** Additional context bag data */
bag: Bag;
}React component for loading external UI components dynamically with stream context.
function LoadExternalComponent(props: LoadExternalComponentProps): React.ReactElement;
interface LoadExternalComponentProps {
/** Stream object from useStream hook */
stream: UseStream<any, any>;
/** UI message to render */
message: UIMessage<any, any>;
/** Optional namespace for component resolution */
namespace?: string;
/** Optional metadata */
meta?: any;
/** Fallback component if loading fails */
fallback?: React.ComponentType<any>;
/** Component registry for custom components */
components?: Record<string, React.ComponentType<any>>;
/** Inline styles */
style?: React.CSSProperties;
/** CSS class name */
className?: string;
}
function useStreamContext<StateType, Bag>(): UseStreamContext<StateType, Bag>;
interface UseStreamContext<StateType, Bag> {
/** Current stream instance */
stream: UseStream<StateType, Bag>;
/** Current message being rendered */
message: UIMessage<any, any>;
/** Namespace context */
namespace?: string;
/** Additional metadata */
meta?: any;
}Type-safe UI message management with reducers and utilities.
interface UIMessage<TName extends string = string, TProps = any> {
/** Message type identifier */
type: TName;
/** Message properties */
props: TProps;
/** Unique message ID */
id: string;
/** Message timestamp */
timestamp: string;
/** Optional message metadata */
metadata?: Record<string, any>;
}
interface RemoveUIMessage {
/** Action type for removal */
type: "remove";
/** ID of message to remove */
id: string;
}
// Type guard functions
function isUIMessage(message: unknown): message is UIMessage;
function isRemoveUIMessage(message: unknown): message is RemoveUIMessage;
// UI message reducer for state management
function uiMessageReducer(
state: UIMessage[],
action: UIMessage | RemoveUIMessage
): UIMessage[];
/**
* Experimental function for loading shared UI components
* @param options - Configuration options for shared component loading
* @returns Promise resolving to loaded shared component
*/
function experimental_loadShare(options: LoadShareOptions): Promise<any>;
interface LoadShareOptions {
/** Component identifier to load */
componentId: string;
/** Optional namespace for component resolution */
namespace?: string;
/** Component loading options */
options?: Record<string, unknown>;
}Server-side helpers for type-safe UI component rendering.
function typedUi<Decl extends Record<string, React.ElementType>>(
config: TypedUiConfig<Decl>,
options?: TypedUiOptions
): TypedUiInstance<Decl>;
interface TypedUiConfig<Decl extends Record<string, React.ElementType>> {
/** Component declarations mapping names to types */
components: Decl;
/** Default namespace for components */
namespace?: string;
}
interface TypedUiOptions {
/** Enable development mode features */
dev?: boolean;
/** Custom component resolver */
resolver?: (name: string) => React.ElementType | undefined;
}
interface TypedUiInstance<Decl extends Record<string, React.ElementType>> {
/** Push a new UI message */
push<K extends keyof Decl>(
name: K,
props: React.ComponentProps<Decl[K]>
): UIMessage<K, React.ComponentProps<Decl[K]>>;
/** Delete a UI message by ID */
delete(id: string): RemoveUIMessage;
/** Current UI message items */
items: UIMessage[];
}interface Branch {
/** Branch identifier */
id: string;
/** Branch name */
name?: string;
/** Parent branch ID */
parentId?: string;
/** Branch checkpoint */
checkpoint: string;
/** Branch metadata */
metadata?: Record<string, any>;
}
interface BranchTree {
/** Root branch */
root: Branch;
/** All branches in tree */
branches: Record<string, Branch>;
/** Current active branch */
current: string;
}
interface HistoryItem<StateType> {
/** History item ID */
id: string;
/** Associated checkpoint */
checkpoint: string;
/** State values at this point */
values: StateType;
/** Timestamp */
timestamp: string;
/** Associated messages */
messages: Message[];
/** Metadata */
metadata?: Record<string, any>;
}
interface MessageMetadata<StateType> {
/** Message ID */
messageId: string;
/** Associated branch */
branch: Branch;
/** State at message time */
state: StateType;
/** Message timestamp */
timestamp: string;
}interface SubmitOptions {
/** Override stream mode */
streamMode?: StreamMode | StreamMode[];
/** Override configuration */
config?: Config;
/** Additional metadata */
metadata?: Metadata;
/** Reset stream state before submit */
reset?: boolean;
}
interface InterruptOptions {
/** Interrupt after specific nodes */
after?: string[];
/** Interrupt before specific nodes */
before?: string[];
/** Interrupt action */
action?: "interrupt" | "rollback";
}
interface JoinStreamOptions {
/** Stream modes to join */
streamMode?: StreamMode | StreamMode[];
/** Enable subgraph streaming */
streamSubgraphs?: boolean;
}import React, { useState } from 'react';
import { useStream } from '@langchain/langgraph-sdk/react';
import { Client } from '@langchain/langgraph-sdk';
const client = new Client({
apiUrl: 'https://api.langgraph.example.com',
apiKey: 'your-api-key'
});
interface AppState {
messages: Array<{ role: string; content: string }>;
context: Record<string, any>;
}
export function ChatInterface() {
const [input, setInput] = useState('');
const stream = useStream<AppState>({
client,
assistantId: 'assistant_123',
streamMode: ['values', 'messages'],
onError: (error) => {
console.error('Stream error:', error);
}
});
const handleSubmit = async () => {
if (!input.trim()) return;
await stream.submit({
message: input,
timestamp: new Date().toISOString()
});
setInput('');
};
return (
<div className="chat-interface">
<div className="messages">
{stream.messages.map((message, index) => (
<div key={index} className={`message ${message.type}`}>
<div className="content">{message.content}</div>
<div className="timestamp">
{new Date(message.timestamp).toLocaleTimeString()}
</div>
</div>
))}
</div>
{stream.isLoading && (
<div className="loading">Processing...</div>
)}
{stream.error && (
<div className="error">
Error: {stream.error.message}
<button onClick={() => window.location.reload()}>
Retry
</button>
</div>
)}
<div className="input-area">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSubmit()}
placeholder="Type your message..."
disabled={stream.isLoading}
/>
<button
onClick={handleSubmit}
disabled={stream.isLoading || !input.trim()}
>
Send
</button>
{stream.isLoading && (
<button onClick={stream.stop}>Stop</button>
)}
</div>
{stream.values && (
<div className="debug-info">
<h3>Current State:</h3>
<pre>{JSON.stringify(stream.values, null, 2)}</pre>
</div>
)}
</div>
);
}import React, { useState, useEffect } from 'react';
import { useStream } from '@langchain/langgraph-sdk/react';
export function AdvancedStreamManager() {
const [selectedBranch, setSelectedBranch] = useState<string | null>(null);
const stream = useStream({
client,
assistantId: 'assistant_advanced',
streamMode: ['values', 'updates', 'messages', 'debug'],
streamSubgraphs: true,
onEvent: (event) => {
console.log('Stream event:', event);
}
});
// Handle branch switching
useEffect(() => {
if (selectedBranch && stream.setBranch) {
const branch = stream.experimental_branchTree?.branches[selectedBranch];
if (branch) {
stream.setBranch(branch);
}
}
}, [selectedBranch, stream]);
const handleInterrupt = async () => {
await stream.interrupt({
after: ['node_1', 'node_2'],
action: 'interrupt'
});
};
const renderBranchTree = () => {
if (!stream.experimental_branchTree) return null;
const { branches, current } = stream.experimental_branchTree;
return (
<div className="branch-tree">
<h3>Execution Branches</h3>
{Object.entries(branches).map(([id, branch]) => (
<div
key={id}
className={`branch ${id === current ? 'active' : ''}`}
onClick={() => setSelectedBranch(id)}
>
<div className="branch-name">
{branch.name || `Branch ${id.slice(0, 8)}`}
</div>
<div className="branch-checkpoint">
Checkpoint: {branch.checkpoint.slice(0, 8)}...
</div>
</div>
))}
</div>
);
};
const renderHistory = () => (
<div className="history">
<h3>Execution History</h3>
{stream.history.map((item, index) => (
<div key={item.id} className="history-item">
<div className="history-header">
<span className="checkpoint">
{item.checkpoint.slice(0, 8)}...
</span>
<span className="timestamp">
{new Date(item.timestamp).toLocaleString()}
</span>
</div>
<div className="history-content">
<pre>{JSON.stringify(item.values, null, 2)}</pre>
</div>
</div>
))}
</div>
);
return (
<div className="advanced-stream-manager">
<div className="controls">
<button
onClick={() => stream.submit({ action: 'analyze' })}
disabled={stream.isLoading}
>
Start Analysis
</button>
<button
onClick={handleInterrupt}
disabled={!stream.isLoading}
>
Interrupt
</button>
<button onClick={stream.stop}>Stop</button>
</div>
<div className="content">
<div className="left-panel">
{renderBranchTree()}
</div>
<div className="main-panel">
<div className="current-state">
<h3>Current State</h3>
{stream.values ? (
<pre>{JSON.stringify(stream.values, null, 2)}</pre>
) : (
<p>No state data</p>
)}
</div>
{stream.messages.length > 0 && (
<div className="messages">
<h3>Messages</h3>
{stream.messages.map((message, index) => (
<div key={index} className="message">
<strong>{message.type}:</strong> {message.content}
</div>
))}
</div>
)}
</div>
<div className="right-panel">
{renderHistory()}
</div>
</div>
</div>
);
}import React from 'react';
import { LoadExternalComponent, useStream } from '@langchain/langgraph-sdk/react';
// Custom UI components
const ChartComponent = ({ data, title }: { data: number[]; title: string }) => (
<div className="chart">
<h3>{title}</h3>
<div className="bars">
{data.map((value, index) => (
<div
key={index}
className="bar"
style={{ height: `${value}px` }}
/>
))}
</div>
</div>
);
const FormComponent = ({ fields, onSubmit }: {
fields: Array<{ name: string; type: string; label: string }>;
onSubmit: (data: Record<string, any>) => void;
}) => {
const [formData, setFormData] = useState({});
return (
<form onSubmit={(e) => { e.preventDefault(); onSubmit(formData); }}>
{fields.map(field => (
<div key={field.name} className="field">
<label>{field.label}</label>
<input
type={field.type}
onChange={(e) => setFormData({
...formData,
[field.name]: e.target.value
})}
/>
</div>
))}
<button type="submit">Submit</button>
</form>
);
};
export function DynamicUIDemo() {
const stream = useStream({
client,
assistantId: 'ui_assistant',
streamMode: ['values', 'custom'],
onEvent: (event) => {
// Handle custom UI events
if (event.event === 'custom' && isUIMessage(event.data)) {
console.log('UI message received:', event.data);
}
}
});
// Component registry
const components = {
chart: ChartComponent,
form: FormComponent
};
const renderUIMessages = () => {
// Extract UI messages from stream
const uiMessages = stream.messages.filter(isUIMessage);
return uiMessages.map(message => (
<LoadExternalComponent
key={message.id}
stream={stream}
message={message}
components={components}
fallback={() => <div>Component not found: {message.type}</div>}
className="dynamic-component"
/>
));
};
return (
<div className="dynamic-ui-demo">
<div className="controls">
<button onClick={() => stream.submit({
action: 'generate_chart',
data: [10, 20, 30, 15, 25]
})}>
Generate Chart
</button>
<button onClick={() => stream.submit({
action: 'create_form',
fields: [
{ name: 'name', type: 'text', label: 'Name' },
{ name: 'email', type: 'email', label: 'Email' }
]
})}>
Create Form
</button>
</div>
<div className="ui-components">
{renderUIMessages()}
</div>
{stream.error && (
<div className="error">Error: {stream.error.message}</div>
)}
</div>
);
}import React, { useReducer, useEffect } from 'react';
import { uiMessageReducer, UIMessage, RemoveUIMessage } from '@langchain/langgraph-sdk/react-ui';
export function UIMessageManager() {
const [uiMessages, dispatchUIMessage] = useReducer(uiMessageReducer, []);
const stream = useStream({
client,
assistantId: 'message_assistant',
onEvent: (event) => {
if (event.event === 'custom') {
const data = event.data;
if (isUIMessage(data)) {
dispatchUIMessage(data);
} else if (isRemoveUIMessage(data)) {
dispatchUIMessage(data);
}
}
}
});
// Simulate adding messages
const addMessage = (type: string, props: any) => {
const message: UIMessage = {
type,
props,
id: `msg_${Date.now()}`,
timestamp: new Date().toISOString()
};
dispatchUIMessage(message);
};
const removeMessage = (id: string) => {
const removeAction: RemoveUIMessage = {
type: 'remove',
id
};
dispatchUIMessage(removeAction);
};
return (
<div className="ui-message-manager">
<div className="controls">
<button onClick={() => addMessage('notification', {
title: 'Success',
message: 'Operation completed',
type: 'success'
})}>
Add Success Notification
</button>
<button onClick={() => addMessage('progress', {
value: 50,
label: 'Processing...'
})}>
Add Progress Bar
</button>
<button onClick={() => {
if (uiMessages.length > 0) {
removeMessage(uiMessages[0].id);
}
}}>
Remove First Message
</button>
</div>
<div className="messages">
{uiMessages.map(message => (
<div key={message.id} className="ui-message">
<div className="message-header">
<span className="type">{message.type}</span>
<button onClick={() => removeMessage(message.id)}>×</button>
</div>
<div className="message-content">
<pre>{JSON.stringify(message.props, null, 2)}</pre>
</div>
</div>
))}
</div>
</div>
);
}// Server-side component definition
import { typedUi } from '@langchain/langgraph-sdk/react-ui/server';
// Define component types
interface ButtonProps {
label: string;
onClick?: () => void;
variant?: 'primary' | 'secondary';
}
interface CardProps {
title: string;
content: string;
actions?: ButtonProps[];
}
// Create typed UI instance
const ui = typedUi({
components: {
button: {} as React.ComponentType<ButtonProps>,
card: {} as React.ComponentType<CardProps>
},
namespace: 'app'
});
// Usage in LangGraph node
function createUIMessage() {
// Type-safe UI message creation
const buttonMessage = ui.push('button', {
label: 'Click me',
variant: 'primary',
onClick: () => console.log('Button clicked')
});
const cardMessage = ui.push('card', {
title: 'Information Card',
content: 'This is some important information',
actions: [
{ label: 'Accept', variant: 'primary' },
{ label: 'Decline', variant: 'secondary' }
]
});
return {
ui_messages: [buttonMessage, cardMessage]
};
}
// Delete UI messages
function cleanupUI() {
const deleteMessage = ui.delete('message_id_123');
return { ui_messages: [deleteMessage] };
}export function RobustStreamingApp() {
const [retryCount, setRetryCount] = useState(0);
const maxRetries = 3;
const stream = useStream({
client,
assistantId: 'reliable_assistant',
onError: (error) => {
console.error('Stream error:', error);
// Automatic retry with backoff
if (retryCount < maxRetries) {
const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
setTimeout(() => {
setRetryCount(prev => prev + 1);
stream.submit({ retry: true, attempt: retryCount + 1 });
}, delay);
}
}
});
const handleManualRetry = () => {
setRetryCount(0);
stream.submit({ manual_retry: true });
};
return (
<div className="robust-streaming-app">
{stream.error && (
<div className="error-banner">
<div className="error-message">
{stream.error.message}
</div>
<div className="error-actions">
{retryCount < maxRetries ? (
<div>Retrying... (Attempt {retryCount + 1}/{maxRetries})</div>
) : (
<button onClick={handleManualRetry}>
Retry Manually
</button>
)}
</div>
</div>
)}
<div className="stream-content">
{stream.values && (
<pre>{JSON.stringify(stream.values, null, 2)}</pre>
)}
</div>
<div className="stream-status">
<div>Loading: {stream.isLoading ? 'Yes' : 'No'}</div>
<div>Error: {stream.error ? 'Yes' : 'No'}</div>
<div>Retry Count: {retryCount}</div>
</div>
</div>
);
}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.
Install with Tessl CLI
npx tessl i tessl/npm-langchain--langgraph-sdk