How Modly Uses React Flow to Power Its Visual Workflow System
Modly leverages the @xyflow/react library to render an interactive node-based canvas, wrapping React Flow’s core graph engine with custom node types, connection validation, and a history system to create a desktop-grade visual workflow editor.
Modly implements its visual workflow system using React Flow to manage complex 3D asset pipelines. The open-source application maintained by lightningpixel/modly extends the library’s primitives with domain-specific logic for drag-and-drop interactions, type-safe connections, and persistent state management.
Core Architecture
The workflow editor centers on WorkflowsPage.tsx, which orchestrates the React Flow provider context and global state.
Canvas Setup and State Management
The entry point wraps the canvas in a ReactFlowProvider to expose the flow context to all child components. According to the source code in src/areas/workflows/WorkflowsPage.tsx, the application initializes node and edge state using React Flow’s native hooks:
const [nodes, setNodes] = useNodesState(workflow.nodes);
const [edges, setEdges] = useEdgesState(workflow.edges);
These state arrays drive the visual graph and are persisted to the workspace on every change. The ReactFlow component receives these states along with change handlers:
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={setNodes}
onEdgesChange={setEdges}
onConnect={(params) => setEdges((eds) => addEdge(params, eds))}
isValidConnection={isValidConnection}
/>
Custom Node Types
Modly defines specialized node components that import useReactFlow to manipulate their own data. In src/areas/workflows/nodes/BaseNode.tsx, the base implementation exposes common UI elements including handles and resize controls. Specific node types extend this pattern:
- ExtensionNode.tsx: Updates extension parameters via
updateNodeData - WhileNode.tsx: Implements container logic using
NodeResizerto group child nodes withparentIdrelationships
Each node calls useReactFlow methods like deleteElements to remove themselves from the graph or updateNodeData to sync configuration changes.
Connection Validation and Custom Edges
The system prevents invalid topologies through the isValidConnection callback defined in WorkflowsPage.tsx. This function checks type compatibility between source and target handles and performs cycle detection by walking upstream edges:
const isValidConnection = useCallback((connection) => {
const srcType = getNodeOutputType(getNode(connection.source), allExtensions);
const tgtType = getNodeInputType(getNode(connection.target), connection.targetHandle, allExtensions);
if (srcType && tgtType && srcType !== tgtType) return false;
// Cycle detection logic
const stack = [connection.target];
const seen = new Set<string>();
while (stack.length) {
const id = stack.pop()!;
if (id === connection.source) return false;
if (seen.has(id)) continue;
seen.add(id);
edges.filter(e => e.source === id).forEach(e => stack.push(e.target));
}
return true;
}, [getNode, allExtensions, edges]);
For rendering, src/areas/workflows/nodes/WorkflowEdge.tsx imports getBezierPath from React Flow to draw curved connections between nodes.
Interaction Patterns
Drag-and-Drop Node Creation
When users drop items onto the canvas, Modly translates screen coordinates to flow coordinates using screenToFlowPosition. The onDrop handler in WorkflowsPage.tsx demonstrates this mapping:
const onDrop = useCallback((e: React.DragEvent) => {
const pos = screenToFlowPosition({ x: e.clientX, y: e.clientY });
const nodeType = e.dataTransfer.getData('modly/node-type');
if (nodeType) {
setNodes((nds) => [
...nds,
{
id: crypto.randomUUID(),
type: nodeType,
position: pos,
data: { enabled: true, params: {} },
},
]);
}
}, [screenToFlowPosition, setNodes]);
This handler supports both individual nodes and container nodes that establish parent-child relationships in the data structure.
State Persistence and History
Auto-Save Integration
Modly maintains a snapshot array of {nodes, edges} and pushes new snapshots after a debounce period. The application calls setNodes and setEdges directly to update React Flow’s internal state, triggering re-renders and persistence logic.
Undo/Redo System
A history stack stores deep copies of the node and edge arrays. Keyboard shortcuts (Ctrl+Z / Ctrl+Y) trigger functions that replace the current state via setNodes and setEdges, effectively rewinding or replaying the workflow graph without reloading the canvas.
Key Source Files
src/areas/workflows/WorkflowsPage.tsx: Hosts the main canvas, React Flow provider, drag-and-drop handlers, connection validation, and undo/redo logic.src/areas/workflows/nodes/BaseNode.tsx: Base node component providing common UI elements and handle positioning.src/areas/workflows/nodes/ExtensionNode.tsx: Specialized node for user-installed extensions that syncs parameter changes viauseReactFlow.src/areas/workflows/nodes/WhileNode.tsx: Container node implementation usingNodeResizerto manage nested workflows.src/areas/workflows/nodes/WorkflowEdge.tsx: Custom edge component utilizinggetBezierPathfor curved connections.
Summary
- Modly builds its visual workflow system on the
@xyflow/reactpackage, utilizingReactFlowProvideranduseReactFlowfor context management. - State is controlled via useNodesState and useEdgesState, with persistence handled through debounced snapshots in
WorkflowsPage.tsx. - Custom node components in the
nodes/directory extend base functionality to support extensions, loops, and 3D asset types. - Connection validation enforces type safety and prevents cycles through the
isValidConnectioncallback before edges are committed. - History management leverages immutable state updates via
setNodesandsetEdgesto enable undo/redo without external state libraries.
Frequently Asked Questions
What React Flow hooks does Modly use for state management?
Modly primarily uses useNodesState and useEdgesState to manage the graph topology, alongside useReactFlow to access imperative methods like screenToFlowPosition, updateNodeData, and deleteElements. These hooks are consumed in WorkflowsPage.tsx and individual node components to coordinate state across the canvas.
How does Modly prevent invalid connections between nodes?
The application implements an isValidConnection callback that inspects node types using getNode to retrieve source and target definitions. It validates type compatibility between handles and performs a depth-first search to detect potential cycles before allowing the connection to form.
Can Modly's workflow editor handle nested or container nodes?
Yes. Modly supports container nodes such as the While loop implemented in WhileNode.tsx. These nodes utilize React Flow’s NodeResizer and establish parent-child relationships through the parentId property in node data, allowing nested workflows within parent containers.
How is the undo/redo functionality implemented?
The system maintains a history stack of node and edge array snapshots in WorkflowsPage.tsx. When users trigger undo or redo shortcuts, the application replaces the current React Flow state by calling setNodes and setEdges with historical snapshots, enabling non-destructive editing without complex state management libraries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →