Uncontrolled Flow
Beginner · 7 min read · Updated Nov 10, 2026
Uncontrolled Flow: Manage Nodes and Edges Without External State
When building a node-based application with React Flow, one important decision is how to manage the state of nodes and edges. React Flow supports two approaches: controlled flows and uncontrolled flows.
In a controlled flow, developers manage nodes and edges using React state and update them through event handlers. In an uncontrolled flow, React Flow manages the node and edge state internally, allowing developers to build interactive diagrams with less state-management code.
If you are developing a flowchart builder, visual workflow editor, diagramming tool, or automation platform, understanding uncontrolled flows can help you choose a simpler and more suitable architecture.
In this guide, you will learn what an uncontrolled flow is, how to implement it, how to update nodes and edges, and when to use it in a React Flow application.
1. What Is an Uncontrolled Flow in React Flow?
An uncontrolled flow is a React Flow implementation where the library manages the internal state of nodes and edges.
Unlike a controlled flow, you do not need to maintain separate React state variables for nodes and edges, or handle every node and edge change through callbacks.
Instead, you provide initial data using:
defaultNodes– Defines the initial nodes in the flow.defaultEdges– Defines the initial connections between nodes.defaultEdgeOptions– Sets default properties for newly created edges.
React Flow handles interactions such as moving nodes, selecting elements, and creating connections internally.
This approach reduces boilerplate code and makes it easier to create simple interactive diagrams.
2. Controlled Flow vs. Uncontrolled Flow
Before implementing an uncontrolled flow, it is important to understand how it differs from a controlled flow.
| Feature | Controlled Flow | Uncontrolled Flow |
|---|---|---|
| State management | Managed by React application | Managed internally by React Flow |
| Initial nodes | nodes | defaultNodes |
| Initial edges | edges | defaultEdges |
| Change handlers | Usually required | Not required for basic interactions |
| Node updates | React state setters | React Flow instance methods |
| Edge updates | React state setters | React Flow instance methods |
| External synchronization | Directly managed by application | Requires additional integration |
Controlled flows are useful when your application needs complete control over graph data, custom validation, undo and redo, collaboration, or synchronization with a backend.
Uncontrolled flows are suitable when you want React Flow to handle common interactions without maintaining a separate copy of the graph state.
3. How to Create an Uncontrolled React Flow
Creating an uncontrolled flow is straightforward. Instead of passing nodes and edges as controlled props, provide defaultNodes and defaultEdges.
Here is a basic example:
import { ReactFlow, type Node, type Edge } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const defaultNodes: Node[] = [
{
id: '1',
position: { x: 100, y: 100 },
data: { label: 'Start Node' },
},
{
id: '2',
position: { x: 350, y: 100 },
data: { label: 'End Node' },
},
];
const defaultEdges: Edge[] = [
{
id: 'e1-2',
source: '1',
target: '2',
},
];
export default function Flow() {
return (
<div style={{ width: '100%', height: '500px' }}>
<ReactFlow
defaultNodes={defaultNodes}
defaultEdges={defaultEdges}
fitView
/>
</div>
);
}
In this example, React Flow initializes the graph using the provided nodes and edges.
You do not need to create useState, onNodesChange, or onEdgesChange handlers just to support basic interactions.
Users can move nodes, select elements, and create connections directly on the canvas.
Important: defaultNodes and defaultEdges are initial values, not continuously synchronized props. Changing the variables passed to them later does not automatically replace the existing internal graph state.
4. Setting Default Options for New Edges
In an uncontrolled flow, React Flow manages new connections internally. You can use defaultEdgeOptions to configure the default appearance and behavior of edges created by users.
For example, you can make every newly created edge animated:
<ReactFlow
defaultNodes={defaultNodes}
defaultEdges={defaultEdges}
defaultEdgeOptions={{
animated: true,
style: {
stroke: '#6366f1',
strokeWidth: 2,
},
}}
fitView
/>
Now, when a user connects two nodes, the newly created edge inherits these default options.
This is particularly useful when you want consistent edge styling without implementing a custom onConnect handler.
5. How to Update Nodes and Edges in an Uncontrolled Flow
One common question is: how can you modify nodes and edges if they are not stored in your component's React state?
React Flow provides an instance API that allows you to interact with its internal graph state.
You can access this API through:
- The
onInitcallback. - The
useReactFlow()hook.
The useReactFlow() hook is a convenient option when you need to access or update the flow from another component.
It provides methods such as:
addNodes()– Adds new nodes.addEdges()– Adds new edges.updateNode()– Updates a node.updateNodeData()– Updates node data.deleteElements()– Deletes selected nodes and edges.getNodes()– Retrieves the current nodes.getEdges()– Retrieves the current edges.
These methods allow you to perform graph operations without maintaining a separate node and edge state.
6. Using useReactFlow() to Add a New Node
Let's create a button that adds a new node at a random position.
First, import the required hook and provider:
import {
ReactFlow,
ReactFlowProvider,
useReactFlow,
} from '@xyflow/react';
Next, create a component that uses useReactFlow():
function AddNodeButton() {
const { addNodes } = useReactFlow();
const handleAddNode = () => {
const newNode = {
id: `node-${Date.now()}`,
position: {
x: Math.random() * 400,
y: Math.random() * 300,
},
data: {
label: 'New Node',
},
};
addNodes(newNode);
};
return (
<button onClick={handleAddNode}>
Add Node
</button>
);
}
The addNodes() method inserts the new node directly into React Flow's internal state.
You can then place the button alongside the canvas:
function Flow() {
return (
<ReactFlowProvider>
<div style={{ width: '100%', height: '500px' }}>
<AddNodeButton />
<ReactFlow
defaultNodes={defaultNodes}
defaultEdges={defaultEdges}
fitView
/>
</div>
</ReactFlowProvider>
);
}
The ReactFlowProvider makes the React Flow instance available to components using useReactFlow().
If your component is already rendered inside an existing React Flow context, you do not need to add another provider.
7. Updating Existing Node Data
Uncontrolled flows also support updating existing node properties and data.
For example, you might want to update a node label when a user clicks a button.
import { useReactFlow } from '@xyflow/react';
function UpdateNodeButton() {
const { updateNodeData } = useReactFlow();
const handleUpdate = () => {
updateNodeData('1', {
label: 'Updated Node',
});
};
return (
<button onClick={handleUpdate}>
Update Node
</button>
);
}
Here, updateNodeData() updates the data associated with the node whose ID is 1.
You can also use updateNode() when you need to update other node properties, such as its position or selection state.
This is useful for building interactive node editors, workflow configuration panels, and dynamic diagramming applications.
8. When Should You Use an Uncontrolled Flow?
Uncontrolled flows are not necessarily better than controlled flows. The right choice depends on how much control your application needs over graph state.
Consider using an uncontrolled flow when:
- You are building a simple flowchart or diagram.
- You want users to move nodes and create connections without additional state-management code.
- You need a lightweight interactive canvas.
- You want to perform occasional graph updates through the React Flow instance API.
- Your application does not need continuous synchronization with external graph state.
Consider a controlled flow when:
- You need to synchronize graph data with a database.
- You are implementing undo and redo functionality.
- You need real-time collaboration between multiple users.
- You require custom node and edge validation.
- You need to maintain application-specific state alongside graph changes.
- You want to control every node and edge update.
For larger workflow applications, controlled state management often provides greater flexibility.
9. Best Practices for Uncontrolled React Flow Applications
Follow these practices when developing an uncontrolled flow:
Use unique node and edge IDs: Every node and edge should have a stable, unique identifier to avoid unexpected updates.
Use default options for consistent styling: Configure defaultEdgeOptions to apply common properties to newly created connections.
Use the React Flow instance API: Use useReactFlow() for adding, updating, retrieving, and deleting graph elements.
Keep provider placement correct: Components using useReactFlow() must be inside a React Flow context, typically through ReactFlowProvider when necessary.
Avoid duplicating graph state: Do not maintain an unnecessary second copy of nodes and edges in React state if React Flow is already managing them internally.
Choose the right architecture early: If your application requires collaboration, history management, or continuous backend synchronization, evaluate whether a controlled flow is more appropriate.
10. Build Interactive Workflow Editors with VisualFlow
Uncontrolled flows provide a simple way to build interactive node-based applications. They are useful for diagrams, flowcharts, and lightweight editors where React Flow can manage most graph interactions internally.
However, production workflow builders often require additional capabilities such as:
- Custom nodes and edges.
- Automatic graph layout using ELK.js or Dagre.
- Undo and redo functionality.
- Nested flows and group nodes.
- Dynamic node configuration.
- Real-time collaboration.
- Database synchronization.
VisualFlow helps developers explore practical React Flow examples, reusable components, and workflow UI patterns. Explore VisualFlow to find inspiration for building interactive diagrams and scalable workflow editors.
Conclusion
React Flow uncontrolled flow is a convenient approach for building interactive diagrams without manually managing node and edge state in React.
By using defaultNodes, defaultEdges, defaultEdgeOptions, and the useReactFlow() hook, developers can create, update, and manage graph elements while allowing React Flow to handle the internal state.
For simple visual editors, uncontrolled flows can reduce boilerplate and speed up development. For advanced workflow applications that require full state control, collaboration, or external synchronization, a controlled flow may be the better choice.