CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langchain--langgraph-sdk

TypeScript/JavaScript SDK for interacting with the LangGraph REST API server

Overview
Eval results
Files

react.mddocs/

React Integration

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.

Core Functionality

The React integration supports:

  • Streaming Hooks: Real-time streaming of LangGraph runs with React state management
  • UI Components: Dynamic loading of external UI components with context
  • Message Management: UI message system with reducers and type safety
  • Server Utilities: Server-side helpers for type-safe UI component rendering
  • Branch Management: Handle execution branches and history in React applications
  • Error Handling: Comprehensive error states and loading management

useStream Hook

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;
}

LoadExternalComponent

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;
}

UI Message System

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 Utilities

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[];
}

Supporting Types

Stream and Branch Management

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;
}

Submit and Interrupt Options

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;
}

Usage Examples

Basic Streaming Hook

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>
  );
}

Advanced Stream Management with Branches

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>
  );
}

Dynamic UI Components

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>
  );
}

UI Message Management with Reducer

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 UI Utilities

// 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] };
}

Error Handling and Recovery

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

docs

assistants.md

auth.md

client.md

crons.md

index.md

react.md

runs.md

store.md

threads.md

tile.json