
Security News
Browserslist-rs Gets Major Refactor, Cutting Binary Size by Over 1MB
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
@jalez/react-flow-automated-layout
Advanced tools
A React library for automated layout of nested node graphs with parent-child relationships using React Flow
A React library for automated layout of nested node graphs with parent-child relationships using React Flow. The library provides an easy-to-use context provider system that handles intelligent layouts for flowcharts and diagrams.
Version 1.0.0 introduces one breaking change that require updates to your code:
The most significant change is how parent-child relationships are managed:
Old API (v0.x):
// Map of parent IDs to arrays of child nodes
const parentIdWithNodes = new Map<string, Node[]>();
// Update on node changes
nodes.forEach((node) => {
if (node.parentId) {
if (!parentIdWithNodes.has(node.parentId)) {
parentIdWithNodes.set(node.parentId, []);
}
parentIdWithNodes.get(node.parentId).push(node);
} else {
if(!parentIdWithNodes.has("no-parent")) {
parentIdWithNodes.set("no-parent", []);
}
parentIdWithNodes.get("no-parent").push(node);
}
});
New API (v1.0.0):
// Map of parent IDs to Sets of child IDs (more efficient lookup)
const nodeParentIdMapWithChildIdSet = new Map<string, Set<string>>();
const nodeIdWithNode = new Map<string, Node>();
// Update on node changes
nodes.forEach((node) => {
// Map for direct node lookup by ID
nodeIdWithNode.set(node.id, node);
// Map parent ID to Set of child IDs
const parentId = node.parentId || "no-parent";
if (!nodeParentIdMapWithChildIdSet.has(parentId)) {
nodeParentIdMapWithChildIdSet.set(parentId, new Set());
}
nodeParentIdMapWithChildIdSet.get(parentId)?.add(node.id);
});
The LayoutProvider component props have changed accordingly:
Old API (v0.x):
<LayoutProvider
// ...other props
parentIdWithNodes={parentIdWithNodes}
nodeIdWithNode={nodeIdWithNode}
>
New API (v1.0.0):
<LayoutProvider
//All props now optional!
>
disableAutoLayoutEffect
prop to LayoutProvider
. This allows you to explicitly disable the automatic layout effect, giving you more control over when layouts are triggered.disableAutoLayoutEffect
is true or if a layout is already in progress, preventing unwanted or redundant layout runs.data.layoutDirection
property on parent nodesnodeIdWithNode
and nodeParentIdMapWithChildIdSet
optional in LayoutProvider
. When not provided, these maps are now managed internally.npm install @jalez/react-flow-automated-layout
First, set up your React Flow component and then wrap it with the LayoutProvider:
import { useState, useCallback } from 'react';
import { ReactFlow, ReactFlowProvider, useNodesState, useEdgesState } from '@xyflow/react';
import { LayoutProvider, LayoutControls } from '@jalez/react-flow-automated-layout';
import '@xyflow/react/dist/style.css';
function FlowDiagram() {
// Set up React Flow states
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
return (
<ReactFlowProvider>
<LayoutProvider>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
{/* Add LayoutControls to your Controls component */}
<Controls position="top-right">
<LayoutControls
showDirectionControls={true}
showAutoLayoutToggle={true}
showSpacingControls={true}
showApplyLayoutButton={true}
/>
</Controls>
<Background />
</ReactFlow>
</LayoutProvider>
</ReactFlowProvider>
);
}
For cases where you need custom control over relationship maps:
import { useState, useCallback, useEffect } from 'react';
import { ReactFlow, ReactFlowProvider, useNodesState, useEdgesState } from '@xyflow/react';
import { LayoutProvider, LayoutControls } from '@jalez/react-flow-automated-layout';
import '@xyflow/react/dist/style.css';
function FlowDiagram() {
// Set up React Flow states
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
// Define a custom key for parentless nodes
const rootLevelKey = "root-level";
// Maps for parent-child relationships (required by LayoutProvider)
const [nodeParentIdMapWithChildIdSet, setNodeParentIdMapWithChildIdSet] = useState(new Map());
const [nodeIdWithNode, setNodeIdWithNode] = useState(new Map());
// Update these maps whenever nodes change
useEffect(() => {
const nodeParentIdMapWithChildIdSet = new Map();
const nodeIdWithNode = new Map();
nodes.forEach((node) => {
// Store node by ID for quick lookup
nodeIdWithNode.set(node.id, node);
// Map parent ID to Set of child IDs, using our custom rootLevelKey for parentless nodes
const parentId = node.parentId || rootLevelKey;
if (!nodeParentIdMapWithChildIdSet.has(parentId)) {
nodeParentIdMapWithChildIdSet.set(parentId, new Set());
}
nodeParentIdMapWithChildIdSet.get(parentId).add(node.id);
});
setNodeParentIdMapWithChildIdSet(nodeParentIdMapWithChildIdSet);
setNodeIdWithNode(nodeIdWithNode);
}, [nodes]);
// Callbacks to update nodes and edges (required by LayoutProvider)
const updateNodesHandler = useCallback((newNodes) => {
setNodes(newNodes);
}, [setNodes]);
const updateEdgesHandler = useCallback((newEdges) => {
setEdges(newEdges);
}, [setEdges]);
return (
<ReactFlowProvider>
<LayoutProvider
initialDirection="DOWN"
initialAutoLayout={true}
initialPadding={50}
initialSpacing={{ node: 50, layer: 50 }}
initialParentResizingOptions={{
padding: {
horizontal: 50,
vertical: 40,
},
minWidth: 150,
minHeight: 150,
}}
updateNodes={updateNodesHandler}
updateEdges={updateEdgesHandler}
nodeParentIdMapWithChildIdSet={nodeParentIdMapWithChildIdSet}
nodeIdWithNode={nodeIdWithNode}
noParentKey={rootLevelKey} // Pass the same custom key for consistency
>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
{/* Add LayoutControls to your Controls component */}
<Controls position="top-right">
<LayoutControls
showDirectionControls={true}
showAutoLayoutToggle={true}
showSpacingControls={true}
showApplyLayoutButton={true}
/>
</Controls>
<Background />
</ReactFlow>
</LayoutProvider>
</ReactFlowProvider>
);
}
To create parent-child relationships, set the parentId
property on child nodes and the extent
property to 'parent':
// Parent node
const parentNode = {
id: 'parent1',
type: 'group',
data: {},
position: { x: 0, y: 0 },
style: {
width: 400,
height: 400,
border: '1px solid #000',
}
};
// Child nodes
const childNodes = [
{
id: 'child1',
data: { label: 'Child 1' },
position: { x: 0, y: 0 },
parentId: 'parent1',
extent: 'parent'
},
{
id: 'child2',
data: { label: 'Child 2' },
position: { x: 0, y: 0 },
parentId: 'parent1',
extent: 'parent'
}
];
// Initialize with both parent and child nodes
const initialNodes = [parentNode, ...childNodes];
The layout system uses default node dimensions for calculating optimal positioning when nodes don't have explicit width and height values. This is an important feature to understand:
When the layout algorithm organizes your nodes, it needs to know how much space each node requires. The library handles this in the following priority order:
style
property (style.width
and style.height
), these values are respected and used for layout calculationsstyle
dimensions are present, the library applies the default dimensions// Default dimensions used internally if not specified on the node's style
const DEFAULT_NODE_WIDTH = 172;
const DEFAULT_NODE_HEIGHT = 36;
Important: For the layout to work correctly, the system relies on the width and height properties of your nodes. The layout engine will use these values when positioning nodes, so it's crucial that:
This prioritization ensures that your custom node sizes are always respected while still allowing the layout algorithm to make accurate spacing calculations for nodes without explicit dimensions.
You can customize these defaults when initializing the LayoutProvider:
<LayoutProvider
// Other props...
initialNodeDimensions={{
width: 200, // Custom default width
height: 50 // Custom default height
}}
>
{/* Your React Flow component */}
</LayoutProvider>
You can adjust node dimensions at runtime using the layout context:
const { setNodeWidth, setNodeHeight } = useLayoutContext();
// Update dimensions
setNodeWidth(180);
setNodeHeight(40);
When the layout algorithm returns updated nodes, it includes these default dimensions in the nodes' properties. This means:
width
and height
properties addedThis is critical to understand because you should use these dimensions when working with the nodes returned by the layout system, rather than assuming nodes have their original dimensions.
Example of a node after layout:
// Original node (no dimensions)
const originalNode = {
id: 'node1',
data: { label: 'Node 1' },
position: { x: 0, y: 0 }
};
// Node after layout (with dimensions added)
const afterLayoutNode = {
id: 'node1',
data: { label: 'Node 1' },
position: { x: 100, y: 200 },
width: 172, // Added by the layout system
height: 36, // Added by the layout system
style: {
width: 172, // Also added to style
height: 36 // Also added to style
}
};
This ensures that node dimensions are consistent across the entire application, leading to more predictable layouts.
You can create your own custom controls by using the useLayoutContext
hook:
import { useLayoutContext } from '@jalez/react-flow-automated-layout';
function CustomLayoutControl() {
const {
direction,
autoLayout,
nodeSpacing,
layerSpacing,
layoutInProgress,
setDirection,
setAutoLayout,
setNodeSpacing,
setLayerSpacing,
applyLayout
} = useLayoutContext();
return (
<div>
<h3>Custom Layout Controls</h3>
{/* Direction controls */}
<div>
<label>Direction:</label>
<div>
{(['DOWN', 'RIGHT', 'UP', 'LEFT']).map((dir) => (
<button
key={dir}
onClick={() => setDirection(dir)}
style={{
background: direction === dir ? '#0041d0' : '#f5f5f5',
color: direction === dir ? 'white' : 'black'
}}
>
{dir}
</button>
))}
</div>
</div>
{/* Spacing control */}
<div>
<label>Spacing: {nodeSpacing}px</label>
<input
type="range"
min="20"
max="200"
value={nodeSpacing}
onChange={(e) => {
const value = parseInt(e.target.value);
setNodeSpacing(value);
setLayerSpacing(value);
}}
/>
</div>
{/* Auto layout toggle */}
<div>
<label>
<input
type="checkbox"
checked={autoLayout}
onChange={() => setAutoLayout(!autoLayout)}
/>
Auto Layout
</label>
</div>
{/* Apply layout button */}
{!autoLayout && (
<button
onClick={() => applyLayout()}
disabled={layoutInProgress}
>
{layoutInProgress ? 'Applying...' : 'Apply Layout'}
</button>
)}
</div>
);
}
Starting with v1.1.0, the library implements a pluggable layout engine architecture that allows for different layout algorithms to be used. Currently, the library ships with the Dagre engine:
import { LayoutProvider, engines } from '@jalez/react-flow-automated-layout';
function FlowDiagram() {
return (
<LayoutProvider
// Optionally specify a different engine (dagre is the default)
engine={engines.dagre}
>
{/* Your React Flow component */}
</LayoutProvider>
);
}
You can now set different layout directions for individual containers by adding a layoutDirection
property to the parent node's data object:
// Parent nodes with different layout directions
const parentNodes = [
{
id: 'container1',
type: 'group',
data: {
label: 'Vertical Layout',
layoutDirection: 'TB' // Top to Bottom layout for this container's children
},
position: { x: 0, y: 0 },
style: { width: 400, height: 400 }
},
{
id: 'container2',
type: 'group',
data: {
label: 'Horizontal Layout',
layoutDirection: 'LR' // Left to Right layout for this container's children
},
position: { x: 500, y: 0 },
style: { width: 400, height: 400 }
}
];
This allows you to create more complex diagrams with different layout directions per section, all while maintaining the global layout algorithm for parent relationships.
The new edge handling system automatically manages connections between nodes in different containers, rerouting edges to the appropriate parent containers when necessary. This works automatically when you create edges between nodes that aren't direct siblings:
// Example edge between nodes in different containers
const crossContainerEdge = {
id: 'edge-cross-container',
source: 'node-in-container1',
target: 'node-in-container2'
};
When this edge is processed by the layout engine, it will intelligently:
This leads to cleaner diagrams with more logical edge paths, especially in complex nested structures.
You can implement your own layout engine by implementing the LayoutEngine interface:
import { LayoutEngine } from '@jalez/react-flow-automated-layout';
const MyCustomEngine: LayoutEngine = {
calculate: async (nodes, edges, options) => {
// Your custom layout algorithm implementation
// Must return positioned nodes with { position: { x, y } }
return layoutedNodes;
}
};
// Then use your engine in the LayoutProvider
<LayoutProvider engine={MyCustomEngine}>
{/* Your React Flow component */}
</LayoutProvider>
The layout engine system provides flexible configuration options:
// Example of configuration options for layout engines
const layoutConfig = {
nodes: nodes,
edges: edges,
dagreDirection: 'TB', // 'TB', 'BT', 'LR', or 'RL'
margin: 20,
nodeSpacing: 50,
layerSpacing: 50,
nodeWidth: 172,
nodeHeight: 36,
layoutHidden: false // Whether to include hidden nodes in layout
};
// Access configuration through the context
const { setLayoutConfig } = useLayoutContext();
setLayoutConfig(layoutConfig);
The github repository includes several examples demonstrating different features:
Demonstrates how LayoutProvider automatically organizes nested nodes with parent-child relationships while maintaining proper spacing and hierarchy.
Shows how LayoutProvider automatically reorganizes the diagram when new nodes are created, keeping the layout clean and organized.
Illustrates how LayoutProvider maintains a coherent layout when nodes are deleted, automatically rearranging connections and preserving the flow.
Demonstrates selective layout application where only selected nodes are reorganized while the rest of the graph remains unchanged, allowing targeted layout adjustments to specific parts of complex diagrams.
Shows how to build your own custom UI controls by accessing the layout context directly via the useLayoutContext hook, enabling fully customized layout interfaces.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | Child components | |
initialDirection | 'UP' | 'DOWN' | 'LEFT' | 'RIGHT' | 'DOWN' | Initial layout direction |
initialAutoLayout | boolean | false | Whether to automatically apply layout on changes |
initialPadding | number | 50 | Padding around the layout |
initialSpacing | { node: number, layer: number } | { node: 50, layer: 50 } | Spacing between nodes and layers |
initialNodeDimensions | { width: number, height: number } | { width: 172, height: 36 } | Default dimensions for nodes without explicit width/height |
initialParentResizingOptions | object | See below | Options for parent container resizing |
updateNodes | (nodes: Node[]) => void | Callback to update nodes | |
updateEdges | (edges: Edge[]) => void | Callback to update edges | |
nodeParentIdMapWithChildIdSet | Map<string, Set> | Map of parent IDs to Sets of child IDs. Must include a key grouping top-level nodes without a parent. | |
nodeIdWithNode | Map<string, Node> | Map of node IDs to node objects | |
noParentKey | string | 'no-parent' | Customizable key used to represent nodes without a parent in the nodeParentIdMapWithChildIdSet map |
{
padding: {
horizontal: 50,
vertical: 40
},
minWidth: 150,
minHeight: 150
}
Prop | Type | Default | Description |
---|---|---|---|
showDirectionControls | boolean | true | Show direction control buttons |
showAutoLayoutToggle | boolean | true | Show auto-layout toggle switch |
showSpacingControls | boolean | true | Show spacing slider controls |
showApplyLayoutButton | boolean | true | Show apply layout button |
import { useLayoutContext } from "@jalez/react-flow-automated-layout";
function MyCustomControl() {
const {
direction,
setDirection,
nodeSpacing,
layerSpacing,
setNodeSpacing,
setLayerSpacing,
applyLayout,
autoLayout,
setAutoLayout,
layoutInProgress
} = useLayoutContext();
// Your custom control implementation
}
Contributions are welcome! Feel free to open issues or submit pull requests to improve the project.
MIT
FAQs
A React library for automated layout of nested node graphs with parent-child relationships using React Flow
The npm package @jalez/react-flow-automated-layout receives a total of 18 weekly downloads. As such, @jalez/react-flow-automated-layout popularity was classified as not popular.
We found that @jalez/react-flow-automated-layout demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
Research
Security News
Eight new malicious Firefox extensions impersonate games, steal OAuth tokens, hijack sessions, and exploit browser permissions to spy on users.
Security News
The official Go SDK for the Model Context Protocol is in development, with a stable, production-ready release expected by August 2025.