Adding Interactivity to Your Flow
This guide shows how to create an interactive flow in Reflex, allowing you to select, drag, and connect nodes and edges.
Define the State
We start by defining the nodes and edges of the flow. The FlowState class holds the nodes and edges as state variables and includes event handlers to respond to changes.
import reflex as rx
import reflex_enterprise as rxe
from reflex_enterprise.components.flow.types import Node, Edge
class FlowState(rx.State):
nodes: list[Node] = [
{
"id": "1",
"type": "input",
"position": {"x": 100, "y": 100},
"data": {"label": "Node 1"},
},
{
"id": "2",
"type": "default",
"position": {"x": 300, "y": 200},
"data": {"label": "Node 2"},
},
]
edges: list[Edge] = [
{
"id": "e1-2",
"source": "1",
"target": "2",
"label": "Connection",
"type": "step",
}
]ExpandCollapse
Add Event Handlers
Event handlers allow the flow to respond to user interactions such as dragging nodes, updating edges, or creating new connections.
@rx.event
def set_nodes(self, nodes: list[Node]):
self.nodes = nodes
@rx.event
def set_edges(self, edges: list[Edge]):
self.edges = edges- set_nodes updates nodes when they are moved or edited.
- set_edges updates edges when they are modified or deleted.
Note that these are plain setters — they receive the already-updated list. The change-application logic lives in the component wiring, shown next.
Render the Interactive Flow
Finally, we render the flow using rxe.flow, passing in the state and event handlers. Additional UI features include zoom/pan controls, a background grid, and a mini-map for navigation.
def interactive_flow():
return rx.box(
rxe.flow(
rxe.flow.controls(),
rxe.flow.background(),
rxe.flow.mini_map(),
nodes=FlowState.nodes,
edges=FlowState.edges,
on_nodes_change=lambda node_changes: FlowState.set_nodes(
rxe.flow.util.apply_node_changes(FlowState.nodes, node_changes)
),
on_edges_change=lambda edge_changes: FlowState.set_edges(
rxe.flow.util.apply_edge_changes(FlowState.edges, edge_changes)
),
on_connect=lambda connection: FlowState.set_edges(
rxe.flow.util.add_edge(connection, FlowState.edges)
),
fit_view=True,
attribution_position="bottom-right",
),
height="100vh",
width="100vw",
)ExpandCollapse
on_nodes_changefires when nodes are dragged, selected, resized, or removed.on_edges_changefires when edges are selected or removed.on_connectfires when the user drags a connection between two handles;rxe.flow.util.add_edgebuilds the new edge list from the connection.
Make sure to set the height and width of the container around the flow — the flow fills its parent, so without explicit dimensions it will not be visible.
Controlled vs. Uncontrolled Flows
Passing nodes and edges makes the flow controlled: your state is the source of truth, and the canvas renders exactly what the state contains. A controlled flow must wire on_nodes_change and on_edges_change as shown above, otherwise user interactions (dragging, selecting, deleting) are discarded.
Passing only default_nodes and default_edges makes the flow uncontrolled: the component manages changes internally, but your state never learns about them.
Where Changes Get Applied
rxe.flow.util.apply_node_changes and rxe.flow.util.apply_edge_changes must be used inside of component code — in the lambda wired to on_nodes_change / on_edges_change — and never inside of state code. They evaluate on the client and produce the updated list, which is then passed to a plain setter event handler:
rxe.flow(
...,
on_nodes_change=lambda changes: FlowState.set_nodes(
rxe.flow.util.apply_node_changes(FlowState.nodes, changes)
),
)Do not call these utilities from within an @rx.event handler; the state side should only receive and store the resulting list.