TypeScript/JavaScript SDK for interacting with the LangGraph REST API server
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.