Skip to main content
CrystalFlow provides several hooks for building custom workflow UIs.

useWorkflow

Manage workflow state and operations.
function useWorkflow(initialWorkflow?: Workflow): {
  workflow: Workflow;
  addNode: (nodeClass: typeof Node, options?: AddNodeOptions) => Node;
  removeNode: (nodeId: string) => void;
  updateNodeData: (nodeId: string, data: Record<string, any>) => void;
  connect: (sourceId: string, sourcePort: string, targetId: string, targetPort: string) => void;
  disconnect: (connectionId: string) => void;
  clear: () => void;
  reset: () => void;
}
Example:
import { useWorkflow } from '@crystalflow/react';
import { AddNode } from './nodes';

function MyComponent() {
  const { workflow, addNode, connect } = useWorkflow();

  const handleAddNode = () => {
    const node = addNode(AddNode, {
      position: { x: 100, y: 100 }
    });
    console.log('Added node:', node.id);
  };

  return <button onClick={handleAddNode}>Add Node</button>;
}

useNodeRegistry

Access the node registry.
function useNodeRegistry(nodes?: Array<typeof Node>): {
  registry: NodeRegistry;
  nodeTypes: Map<string, typeof Node>;
  getNodeClass: (type: string) => typeof Node | undefined;
  getNodeMetadata: (type: string) => NodeMetadata | undefined;
}
Example:
import { useNodeRegistry } from '@crystalflow/react';
import { AddNode, MultiplyNode } from './nodes';

function NodeList() {
  const { nodeTypes, getNodeMetadata } = useNodeRegistry([
    AddNode,
    MultiplyNode
  ]);

  return (
    <ul>
      {Array.from(nodeTypes.keys()).map(type => {
        const metadata = getNodeMetadata(type);
        return <li key={type}>{metadata?.label}</li>;
      })}
    </ul>
  );
}

useExecution

Execute workflows with event handlers.
function useExecution(): {
  executor: Executor;
  isExecuting: boolean;
  result: ExecutionResult | null;
  error: Error | null;
  execute: (workflow: Workflow, options?: ExecutionOptions) => Promise<ExecutionResult>;
  cancel: (executionId: string) => void;
  reset: () => void;
}
Example:
import { useExecution } from '@crystalflow/react';

function ExecuteButton({ workflow }) {
  const { execute, isExecuting, result, error } = useExecution();

  const handleExecute = async () => {
    try {
      const executionResult = await execute(workflow, {
        timeout: 30000
      });
      console.log('Success!', executionResult);
    } catch (err) {
      console.error('Failed:', err);
    }
  };

  return (
    <div>
      <button onClick={handleExecute} disabled={isExecuting}>
        {isExecuting ? 'Executing...' : 'Execute'}
      </button>
      
      {result && <div>Status: {result.status}</div>}
      {error && <div>Error: {error.message}</div>}
    </div>
  );
}

useReactFlowState

Manage React Flow nodes and edges state.
function useReactFlowState(workflow: Workflow): {
  nodes: ReactFlowNode[];
  edges: ReactFlowEdge[];
  onNodesChange: OnNodesChange;
  onEdgesChange: OnEdgesChange;
  onConnect: OnConnect;
}
Example:
import { useReactFlowState } from '@crystalflow/react';
import { ReactFlow } from 'reactflow';

function CustomCanvas({ workflow }) {
  const { nodes, edges, onNodesChange, onEdgesChange, onConnect } =
    useReactFlowState(workflow);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
    />
  );
}

useWorkflowExecution

Combined workflow execution with progress tracking.
function useWorkflowExecution(workflow: Workflow): {
  execute: () => Promise<void>;
  isExecuting: boolean;
  progress: number;
  result: ExecutionResult | null;
  error: Error | null;
  cancel: () => void;
}
Example:
import { useWorkflowExecution } from '@crystalflow/react';

function ExecutionPanel({ workflow }) {
  const { execute, isExecuting, progress, result, error, cancel } =
    useWorkflowExecution(workflow);

  return (
    <div>
      <button onClick={execute} disabled={isExecuting}>
        Execute
      </button>
      
      {isExecuting && (
        <>
          <progress value={progress} max={100} />
          <button onClick={cancel}>Cancel</button>
        </>
      )}
      
      {result && <div>Completed in {result.duration}ms</div>}
      {error && <div>Error: {error.message}</div>}
    </div>
  );
}

useWorkflowFileOperations

Save and load workflows to/from files.
function useWorkflowFileOperations(workflow: Workflow): {
  save: (filename?: string) => Promise<void>;
  load: (file: File) => Promise<Workflow>;
  saveToJson: () => string;
  loadFromJson: (json: string) => Workflow;
}
Example:
import { useWorkflowFileOperations } from '@crystalflow/react';

function FileMenu({ workflow, onLoad }) {
  const { save, load } = useWorkflowFileOperations(workflow);

  const handleSave = async () => {
    await save('my-workflow.json');
  };

  const handleLoad = async (event) => {
    const file = event.target.files[0];
    const loadedWorkflow = await load(file);
    onLoad(loadedWorkflow);
  };

  return (
    <div>
      <button onClick={handleSave}>Save</button>
      <input type="file" onChange={handleLoad} accept=".json" />
    </div>
  );
}

useNodePalette

Manage node palette drag-and-drop.
function useNodePalette(nodeTypes: Array<typeof Node>): {
  categories: Map<string, typeof Node[]>;
  onDragStart: (event: React.DragEvent, nodeClass: typeof Node) => void;
  onDragEnd: (event: React.DragEvent) => void;
}
Example:
import { useNodePalette } from '@crystalflow/react';

function NodePalette({ nodeTypes }) {
  const { categories, onDragStart, onDragEnd } = useNodePalette(nodeTypes);

  return (
    <div>
      {Array.from(categories.entries()).map(([category, nodes]) => (
        <div key={category}>
          <h3>{category}</h3>
          {nodes.map((NodeClass) => {
            const metadata = Reflect.getMetadata('node:definition', NodeClass);
            return (
              <div
                key={metadata.type}
                draggable
                onDragStart={(e) => onDragStart(e, NodeClass)}
                onDragEnd={onDragEnd}
              >
                {metadata.label}
              </div>
            );
          })}
        </div>
      ))}
    </div>
  );
}

Complete Example

import React from 'react';
import {
  useWorkflow,
  useExecution,
  useNodeRegistry,
  useWorkflowFileOperations
} from '@crystalflow/react';
import { AddNode, MultiplyNode } from './nodes';

function CustomWorkflowBuilder() {
  const { workflow, addNode, connect, clear } = useWorkflow();
  const { nodeTypes } = useNodeRegistry([AddNode, MultiplyNode]);
  const { execute, isExecuting, result } = useExecution();
  const { save, load } = useWorkflowFileOperations(workflow);

  const handleAddNode = (NodeClass: typeof Node) => {
    addNode(NodeClass, {
      position: { x: Math.random() * 500, y: Math.random() * 500 }
    });
  };

  const handleExecute = async () => {
    await execute(workflow);
  };

  const handleSave = async () => {
    await save('workflow.json');
  };

  const handleLoad = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      await load(file);
    }
  };

  return (
    <div>
      <div className="toolbar">
        <button onClick={handleExecute} disabled={isExecuting}>
          {isExecuting ? 'Executing...' : 'Execute'}
        </button>
        <button onClick={handleSave}>Save</button>
        <input type="file" onChange={handleLoad} accept=".json" />
        <button onClick={clear}>Clear</button>
      </div>

      <div className="palette">
        <h3>Node Types</h3>
        {Array.from(nodeTypes.values()).map((NodeClass) => {
          const metadata = Reflect.getMetadata('node:definition', NodeClass);
          return (
            <button
              key={metadata.type}
              onClick={() => handleAddNode(NodeClass)}
            >
              Add {metadata.label}
            </button>
          );
        })}
      </div>

      {result && (
        <div className="result">
          <h3>Result</h3>
          <pre>{JSON.stringify(result, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

export default CustomWorkflowBuilder;