DevTools and Debugging
Advanced · 10 min read · Updated Nov 10, 2026
React Flow DevTools and Debugging: Inspect Nodes, Track Changes, and Debug Workflows
Building a node-based application with React Flow involves more than creating nodes and connecting edges. As an application becomes more complex, developers need to understand how nodes behave, how edges change, how the viewport moves, and how internal state is updated during user interactions.
Debugging these behaviors can become challenging, especially when working with custom nodes, dynamic connections, complex workflows, and controlled React Flow applications.
This is where React Flow DevTools and debugging techniques become useful.
React Flow provides access to internal state through hooks and utilities that allow developers to inspect nodes, monitor changes, and track viewport transformations. These capabilities make it easier to identify problems and understand how a workflow behaves.
In this guide, we will explore how to build debugging tools for React Flow, inspect node information, track node and edge changes, monitor viewport movements, and create a better debugging experience for complex workflow editors.
What Are React Flow DevTools?
React Flow DevTools are utilities that help developers inspect and understand the internal behavior of a node-based application.
They provide visibility into information that is not always obvious from the visual canvas.
For example, a workflow may look correct, but internally:
- A node might have unexpected dimensions.
- An edge might connect to the wrong handle.
- A node position might change unexpectedly.
- The viewport might have an incorrect zoom level.
- A controlled flow might not update its state correctly.
Debugging tools help developers identify these issues by displaying the information behind the visual representation.
The official React Flow documentation presents three experimental debugging utilities:
- Node Inspector: Displays information about nodes in the flow.
- Change Logger: Tracks changes dispatched through React Flow.
- Viewport Logger: Displays the current viewport position and zoom level.
These utilities are independent components, meaning developers can adapt them to their own applications.
Why Is Debugging Important in React Flow?
A simple diagram with a few nodes is relatively easy to maintain. However, a production workflow editor may contain hundreds of nodes, custom handles, conditional connections, nested groups, and dynamic data.
Without proper debugging tools, identifying the source of an issue can take considerable time.
Consider a workflow automation application where a user connects several nodes:
- Trigger Node
- Data Processing Node
- Condition Node
- Action Node
If the final action does not execute correctly, the problem could be related to node data, connection changes, or workflow state.
A debugging panel helps developers inspect these elements without manually searching through the entire application.
Some important benefits include:
| Debugging capability | Developer benefit |
|---|---|
| Node inspection | Understand node properties and dimensions |
| Change tracking | Identify when and why nodes or edges change |
| Viewport monitoring | Debug zoom and canvas positioning |
| State inspection | Understand React Flow's internal behavior |
| Custom debugging panels | Build development tools for complex applications |
1. Build a Node Inspector in React Flow
A Node Inspector is a debugging component that displays information about the nodes currently present in a flow.
React Flow provides the useNodes() hook to access the current nodes.
You can use this hook to inspect:
- Node ID
- Node type
- Node position
- Node dimensions
- Node data
- Selection state
- Dragging state
Understanding the useNodes() hook
The useNodes() hook returns the current nodes in the flow and subscribes the component to changes in that collection.
Whenever nodes change, the component using this hook can re-render.
This behavior makes it useful for debugging because the inspector automatically reflects changes in the canvas.
However, it can also cause unnecessary rendering in large applications. For that reason, useNodes() is generally better suited to debugging panels than performance-sensitive production components.
Create a Node Inspector component
import { useNodes } from '@xyflow/react';
function NodeInspector() {
const nodes = useNodes();
return (
<div className="node-inspector">
<h3>Node Inspector</h3>
{nodes.map((node) => (
<div key={node.id} className="node-info">
<strong>{node.data.label ?? node.id}</strong>
<p>ID: {node.id}</p>
<p>Type: {node.type ?? 'default'}</p>
<p>
Position: {node.position.x}, {node.position.y}
</p>
<p>
Width: {node.measured?.width ?? 'Not measured'}
</p>
<p>
Height: {node.measured?.height ?? 'Not measured'}
</p>
<p>
Selected: {node.selected ? 'Yes' : 'No'}
</p>
</div>
))}
</div>
);
}
This example displays the most useful information about each node.
Notice that it uses node.measured?.width and node.measured?.height to access the dimensions calculated by React Flow after the node has been measured.
The optional chaining prevents errors when dimensions are not available yet.
When should you use a Node Inspector?
A Node Inspector is especially useful when:
- Custom nodes have unexpected dimensions.
- Nodes are not positioned correctly.
- Node data is not updating as expected.
- Selection or dragging behavior is inconsistent.
- You are building a complex workflow editor.
For more advanced debugging interfaces, you can also display node data as formatted JSON.
2. Use ViewportPortal to Display Debugging Information on the Canvas
Sometimes, displaying debugging information in a separate sidebar is not enough.
You may want to show information directly inside the React Flow canvas.
React Flow provides the ViewportPortal component for rendering elements inside the viewport coordinate system.
This means the content moves and scales together with the rest of the flow when users pan or zoom.
For example, you can display node IDs, debug labels, or coordinate information directly over the canvas.
import { ViewportPortal } from '@xyflow/react';
function CanvasDebugInfo() {
return (
<ViewportPortal>
<div
style={{
position: 'absolute',
top: 20,
left: 20,
background: '#111827',
color: '#fff',
padding: '8px 12px',
borderRadius: 8,
fontSize: 12,
}}
>
Canvas Debug Mode
</div>
</ViewportPortal>
);
}
Add the component inside your React Flow instance:
<ReactFlow nodes={nodes} edges={edges}>
<CanvasDebugInfo />
</ReactFlow>
This approach is useful for creating visual debugging overlays.
You can extend it to display:
- Node IDs and types.
- Connection information.
- Node coordinates.
- Selection indicators.
- Custom debug labels.
3. Track Node and Edge Changes with a Change Logger
React Flow communicates changes to nodes and edges through callbacks.
The two important callbacks are:
onNodesChangeonEdgesChange
These callbacks are particularly important when working with controlled flows, where your application manages the node and edge state.
For example, dragging a node generates a position change. Selecting a node generates a selection change. Removing an edge generates an edge removal change.
A Change Logger helps developers understand when these events happen and what information they contain.
Understand NodeChange and EdgeChange
React Flow provides structured change objects.
A node change may represent:
- Position updates.
- Selection changes.
- Dimension updates.
- Node removal.
- Node addition or replacement.
An edge change may represent:
- Edge selection.
- Edge removal.
- Edge addition or replacement.
These changes can be inspected through the corresponding callbacks.
Create a basic Change Logger
import {
useCallback,
useState,
} from 'react';
import {
ReactFlow,
applyNodeChanges,
applyEdgeChanges,
} from '@xyflow/react';
function FlowEditor() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const onNodesChange = useCallback((changes) => {
console.log('Node changes:', changes);
setNodes((currentNodes) =>
applyNodeChanges(changes, currentNodes)
);
}, []);
const onEdgesChange = useCallback((changes) => {
console.log('Edge changes:', changes);
setEdges((currentEdges) =>
applyEdgeChanges(changes, currentEdges)
);
}, []);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
/>
);
}
This example logs every node and edge change before applying it to the application state.
The applyNodeChanges() and applyEdgeChanges() utilities ensure that the incoming changes are correctly applied to the existing arrays.
Why is a Change Logger useful?
Imagine a user dragging a node across the canvas.
React Flow dispatches position changes through onNodesChange. The logger displays the change object in the browser console.
This allows you to verify:
- Which node changed.
- What type of change occurred.
- Whether its position was updated.
- Whether the application state received the change.
The same approach can be used to debug edge creation, deletion, and selection.
4. Create a Reusable Change Logger Component
Instead of writing debugging logic directly inside your main workflow component, you can separate it into reusable components.
A simple logging function can be reused across different applications:
function logNodeChanges(changes) {
changes.forEach((change) => {
console.log(
`[Node Change] ${change.type}`,
change
);
});
}
function logEdgeChanges(changes) {
changes.forEach((change) => {
console.log(
`[Edge Change] ${change.type}`,
change
);
});
}
Then integrate the functions into your callbacks:
const onNodesChange = useCallback((changes) => {
logNodeChanges(changes);
setNodes((nodes) =>
applyNodeChanges(changes, nodes)
);
}, []);
const onEdgesChange = useCallback((changes) => {
logEdgeChanges(changes);
setEdges((edges) =>
applyEdgeChanges(changes, edges)
);
}, []);
This keeps debugging logic separate from state management.
For production applications, you can expand the logger to include timestamps, node IDs, event categories, and structured log levels.
5. Monitor the React Flow Viewport with a Viewport Logger
The viewport controls how users see and navigate the diagram.
It contains three important values:
x: Horizontal translation.y: Vertical translation.zoom: Current zoom level.
When users pan or zoom the canvas, these values change.
React Flow provides the useViewport() hook to access the current viewport state.
Create a Viewport Logger
import {
Panel,
useViewport,
} from '@xyflow/react';
function ViewportLogger() {
const { x, y, zoom } = useViewport();
return (
<Panel position="top-right">
<div className="viewport-debug">
<h4>Viewport Information</h4>
<p>X: {x.toFixed(2)}</p>
<p>Y: {y.toFixed(2)}</p>
<p>Zoom: {zoom.toFixed(2)}</p>
</div>
</Panel>
);
}
You can add this component to your React Flow instance:
<ReactFlow nodes={nodes} edges={edges}>
<ViewportLogger />
</ReactFlow>
The component displays the current viewport values in the top-right corner.
As users pan and zoom, the displayed values update.
Why is viewport debugging important?
Viewport issues can create confusing user experiences.
For example:
- A workflow opens with nodes outside the visible area.
- Zoom controls behave unexpectedly.
- A saved workflow restores to the wrong position.
- A canvas does not display the expected section of a large diagram.
A Viewport Logger makes these issues easier to investigate.
It is also useful when implementing custom zoom controls, workflow restoration, and canvas navigation.
6. Access React Flow's Internal Store for Advanced Debugging
React Flow maintains internal state to manage nodes, edges, selections, viewport information, and other application behavior.
For advanced debugging, React Flow provides the useStore() and useStoreApi() hooks.
These hooks allow developers to access the internal store.
Using useStore()
The useStore() hook subscribes a component to selected values from the React Flow store.
For example, you can read the current viewport transformation:
import { useStore } from '@xyflow/react';
function InternalStateInspector() {
const transform = useStore(
(state) => state.transform
);
return (
<div>
<p>Viewport X: {transform[0]}</p>
<p>Viewport Y: {transform[1]}</p>
<p>Zoom: {transform[2]}</p>
</div>
);
}
The transform is represented as an array containing the horizontal translation, vertical translation, and zoom factor.
Selecting only the required state helps avoid subscribing to unrelated store updates.
Using useStoreApi()
The useStoreApi() hook provides access to the store API, including methods for reading and updating store state.
import { useStoreApi } from '@xyflow/react';
function StoreDebugButton() {
const store = useStoreApi();
const inspectStore = () => {
const state = store.getState();
console.log('Current nodes:', state.nodes);
console.log('Current edges:', state.edges);
console.log('Current viewport:', state.transform);
};
return (
<button onClick={inspectStore}>
Inspect Flow State
</button>
);
}
This example reads the current store state when the button is clicked.
Important: Internal store access is powerful, but it should be used carefully. Prefer documented hooks and callbacks for normal application logic. Avoid directly modifying internal state unless you understand the consequences and are following the supported API.
7. Combine Debugging Tools into a React Flow DevTools Panel
The real advantage of these utilities comes when you combine them into one debugging interface.
A developer panel can display:
- Total node count.
- Total edge count.
- Selected nodes.
- Node dimensions.
- Viewport coordinates.
- Zoom level.
- Recent node changes.
- Recent edge changes.
A simple development layout might look like this:
import {
ReactFlow,
ReactFlowProvider,
} from '@xyflow/react';
function FlowDevTools() {
return (
<ReactFlowProvider>
<div className="flow-editor">
<ReactFlow
nodes={initialNodes}
edges={initialEdges}
>
<NodeInspector />
<ViewportLogger />
<CanvasDebugInfo />
</ReactFlow>
</div>
</ReactFlowProvider>
);
}
This example illustrates how debugging components can be composed around a flow.
In a complete application, the inspector and logger components should be implemented with the appropriate layout and state management. For example, a Node Inspector rendered as a sidebar can use a regular panel outside the canvas, while ViewportPortal is suitable for overlays that should move with the viewport.
The result is a more transparent development environment where developers can inspect the workflow without repeatedly opening the browser console.
8. Best Practices for Debugging React Flow Applications
As your workflow editor grows, debugging tools should remain useful without creating additional performance problems.
Here are some recommendations.
Use debugging components during development
Components that subscribe to all nodes or edges can cause frequent re-renders.
Keep them inside development-only tooling when possible.
Select only the state you need
When using useStore(), use a selector to subscribe to specific values rather than the entire internal store.
This helps reduce unnecessary component updates.
Separate debugging from application logic
Avoid mixing logging and inspection logic with workflow execution or business logic.
Keep debugging components modular so they can be reused and removed independently.
Track meaningful changes
Logging every event in a large workflow can generate excessive console output.
Consider filtering events by type, node ID, or severity.
Avoid unnecessary internal state updates
Use the public React Flow APIs for normal node and edge operations. Internal store access should primarily support advanced inspection and debugging use cases.
Test complex interactions
Test node dragging, edge connections, reconnections, selection, deletion, zooming, and viewport restoration separately.
This helps identify issues before they affect users.
9. Common React Flow Issues You Can Debug with DevTools
| Problem | What to inspect |
|---|---|
| Node appears in the wrong position | Node position and change events |
| Node dimensions are incorrect | Measured width and height |
| Edge connects unexpectedly | Edge source, target, and handle IDs |
| Node selection does not update | Selection changes |
| Canvas opens at the wrong location | Viewport translation and zoom |
| Node data is not synchronized | Node data and state updates |
| Dragging causes unexpected behavior | Position changes and event sequence |
| Workflow restoration is incorrect | Saved nodes, edges, and viewport |
A systematic debugging approach makes it easier to identify whether an issue originates in the React Flow state, custom node implementation, event handling, or application-level logic.
10. Build Better Workflow Editors with VisualFlow
Creating a powerful workflow editor requires more than a canvas with connected nodes. Developers need a reliable architecture for custom nodes, edges, state management, interaction handling, and debugging.
VisualFlow provides React Flow examples and ready-to-use templates for building advanced node-based applications.
Explore practical examples and implementation ideas for:
- AI workflow builders.
- Marketing automation editors.
- Custom node and edge systems.
- Nested and grouped workflows.
- Interactive diagram editors.
- Visual programming interfaces.
These examples can help developers understand how different React Flow features work together when building production-ready applications.
Visit VisualFlow to explore React Flow templates and workflow builder examples.
Conclusion
Debugging is an essential part of building reliable React Flow applications.
With tools such as Node Inspector, Change Logger, and Viewport Logger, developers can gain better visibility into the internal behavior of their workflows.
By using hooks like useNodes(), useViewport(), and useStore(), along with callbacks such as onNodesChange and onEdgesChange, you can identify unexpected changes, inspect node properties, and understand viewport behavior.
As your application becomes more complex, these debugging techniques can save development time and make your workflow editor easier to maintain.
The goal is simple: make the internal behavior of your visual application easier to understand, inspect, and improve.