Mount a typed Grafloria flow in React, keep its nodes and edges in React state, and retain the live diagram instance for imperative actions.
Grafloria has one headless model behind its framework bindings: React renders the diagram, while the instance owns the live graph and engine behavior.
Prerequisites
- React 17, 18, or 19
- A React project with TypeScript and a browser build
- Node.js 20.19 or later for the package toolchain
1. Install the packages
Install the React binding and its renderer and engine dependencies:
bashnpm install @grafloria/react @grafloria/element @grafloria/renderer @grafloria/engine react react-dom
The starter uses the published @grafloria/react package and does not require a renderer stylesheet import.
2. Mount a flow
Use GrafloriaFlow as an element. Type the node data with NodeSpec, give the nodes positions and sizes, and connect them with edge specs.
tsximport { GrafloriaFlow, type NodeSpec } from '@grafloria/react';
const nodes: NodeSpec[] = [
{
id: 'a',
position: { x: 60, y: 80 },
size: { width: 180, height: 80 },
data: { label: 'Ingest' },
},
{
id: 'b',
position: { x: 380, y: 80 },
size: { width: 180, height: 80 },
data: { label: 'Publish' },
},
];
const edges = [{ id: 'e1', source: 'a', target: 'b' }];
export default function App() {
return (
<div style={{ height: '100vh' }}>
<GrafloriaFlow
defaultNodes={nodes}
defaultEdges={edges}
plugins
/>
</div>
);
}
This renders two boxes joined by an edge. The boxes can be dragged, the canvas can be panned and zoomed, and plugins adds the minimap, zoom and fit controls, and background. defaultNodes and defaultEdges seed an uncontrolled flow; the mounted instance owns those values after initialization. Give the host a resolved height; see Edit Mermaid diagrams for the blank-canvas pitfall.
3. Keep nodes and edges in React state
Choose controlled state when another part of your application needs the graph. useNodesState and useEdgesState each return the current specs, a setter, and the handler that folds live model changes back into React state. Pass the third value to the matching onNodesChange or onEdgesChange prop.
tsximport { useRef } from 'react';
import {
GrafloriaFlow,
useEdgesState,
useNodesState,
type NodeSpec,
} from '@grafloria/react';
import type { DiagramInstance } from '@grafloria/react';
const initialNodes: NodeSpec[] = [
{
id: 'a',
position: { x: 60, y: 80 },
size: { width: 180, height: 80 },
data: { label: 'Ingest' },
},
{
id: 'b',
position: { x: 380, y: 80 },
size: { width: 180, height: 80 },
data: { label: 'Publish' },
},
];
const initialEdges = [{ id: 'e1', source: 'a', target: 'b' }];
export default function Editor() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const instanceRef = useRef<DiagramInstance | null>(null);
return (
<div style={{ height: '100vh' }}>
<button
type="button"
onClick={() => {
setNodes((all) => [
...all,
{
id: `node-${all.length}`,
position: { x: 220, y: 220 },
size: { width: 180, height: 80 },
data: { label: 'Review' },
},
]);
}}
>
Add node
</button>
<button
type="button"
onClick={() => instanceRef.current?.fitView()}
>
Fit view
</button>
<GrafloriaFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onInit={(instance) => {
instanceRef.current = instance;
}}
plugins
/>
</div>
);
}
Dragging a node updates the live model and then updates nodes through onNodesChange; edge edits follow the same path through onEdgesChange. Clicking Add node produces a third box because setNodes changes React's source of truth. Keep the arrays in state or memoize them rather than creating a fresh array for every unrelated render.
Do not pass nodes or edges without their matching change handler: the next controlled render can replace a user edit with stale React state. Use defaultNodes and defaultEdges instead when the canvas alone owns the graph.
4. Use the live instance
onInit receives the mounted DiagramInstance. Store it in a ref when a control lives beside the flow. fitView() frames the content; other instance methods let you read the model or reach the engine without replacing the rendered component.
The editor above calls instanceRef.current?.fitView() from Fit view, so the visible boxes are reframed without changing the node or edge state. The ref is nullable because the instance does not exist until the flow mounts.
What you have at the end
You have a React flow with typed node specs, a connected edge, controlled node and edge updates, and a ref to the live instance. Try the complete tutorial in the React in 10 minutes live demo.
Next, see React hooks and components for selection, viewport subscriptions, and custom nodes, or Instance and lifecycle for instance ownership and cleanup.
Was this page helpful?