Build a working diagram in React, connect sibling UI to its live instance, and render kit diagrams and comment threads.
One headless model drives every framework binding: the React components are framework-specific skins over the same diagram engine.
Prerequisites
Use a browser-based React app with React and React DOM 17, 18, or 19. The @grafloria/react 0.10.6 package declares peers @grafloria/engine ^0.3.0, @grafloria/renderer ^0.4.16, and @grafloria/element ^0.4.3.
Install the binding and its peers:
bashnpm install @grafloria/react @grafloria/engine @grafloria/renderer @grafloria/element react react-dom
1. Mount a flow
Use GrafloriaFlow for an interactive node-and-edge canvas. The starter data uses NodeSpec and EdgeSpec; defaultNodes and defaultEdges seed an uncontrolled diagram, which the instance owns after mount.
tsximport { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{ id: 'ingest', position: { x: 60, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Ingest' } },
{ id: 'publish', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Publish' } },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'ingest', target: 'publish' }];
export default function App() {
return (
<div style={{ height: '100vh' }}>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} plugins />
</div>
);
}
The mounted canvas shows Ingest connected to Publish, with the minimap, zoom and fit controls, and background supplied by plugins. Give the wrapper a resolved height; the canvas fills its parent.
2. Keep controlled data in React state
When application UI owns the graph, useNodesState and useEdgesState provide state plus a separate callback for changes coming back from the canvas. Keep both callback connections: without onNodesChange, for example, a node drag can be overwritten by stale controlled state on the next render.
tsximport { GrafloriaFlow, useEdgesState, useNodesState } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const initialNodes: NodeSpec[] = [
{ id: 'ingest', position: { x: 60, y: 80 }, size: { width: 180, height: 80 }, label: 'Ingest' },
{ id: 'publish', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, label: 'Publish' },
];
const initialEdges: EdgeSpec[] = [{ id: 'e1', source: 'ingest', target: 'publish' }];
export default function ControlledFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, , onEdgesChange] = useEdgesState(initialEdges);
return (
<div style={{ height: 440, display: 'flex', flexDirection: 'column' }}>
<button
onClick={() => setNodes((all) => [
...all,
{ id: `step-${all.length}`, position: { x: 220, y: 240 }, label: 'New step' },
])}
>
Add node
</button>
<GrafloriaFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
style={{ flex: 1, minHeight: 0 }}
/>
</div>
);
}
The button adds a node to React state, and dragging or deleting nodes and edges sends the resulting models back through the third tuple values. The second tuple value updates React's specs; it is not the canvas change callback.
3. Reach the canvas from sibling UI
Wrap a toolbar and canvas in GrafloriaProvider when the toolbar is outside the flow subtree. useGrafloria returns the live DiagramInstance, or null until the flow mounts. The toolbar below calls fitView() on that instance.
For live readouts, useSelection returns the current selection as state, useOnSelectionChange runs a callback when it changes, and useViewport returns the camera state. useGrafloriaStore exposes the nearest store itself; use it when you need the store rather than only its instance.
tsximport { useState } from 'react';
import {
GrafloriaFlow,
GrafloriaProvider,
useGrafloria,
useGrafloriaStore,
useOnSelectionChange,
useSelection,
useViewport,
} from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{ id: 'draft', position: { x: 40, y: 80 }, label: 'Draft' },
{ id: 'review', position: { x: 280, y: 80 }, label: 'Review' },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'draft', target: 'review' }];
function Toolbar() {
const instance = useGrafloria();
return (
<button disabled={!instance} onClick={() => instance?.fitView()}>
Fit diagram
</button>
);
}
function CanvasReadout() {
const store = useGrafloriaStore();
const instance = useGrafloria();
const { nodes: selectedNodes } = useSelection();
const { zoom } = useViewport();
const [selectionEvents, setSelectionEvents] = useState(0);
useOnSelectionChange(() => setSelectionEvents((count) => count + 1));
const storeConnected = instance !== null && store?.get() === instance;
return (
<output>
{storeConnected ? 'Canvas connected' : 'Waiting for canvas'} ·{' '}
{selectedNodes.length} selected · {Math.round(zoom * 100)}% zoom ·{' '}
{selectionEvents} selection changes
</output>
);
}
export default function App() {
return (
<GrafloriaProvider>
<div style={{ height: 440, display: 'flex', flexDirection: 'column' }}>
<Toolbar />
<CanvasReadout />
<GrafloriaFlow
defaultNodes={nodes}
defaultEdges={edges}
style={{ flex: 1, minHeight: 0 }}
/>
</div>
</GrafloriaProvider>
);
}
The toolbar is a sibling of the canvas, and Fit diagram frames its nodes when clicked. The readout shows the selected-node count and zoom; selecting a node updates the count and selection-change total. A single flow can also publish its instance to its own children without a provider; use the provider for components outside that subtree.
4. Render a kit diagram
Use GrafloriaDiagram when you have a diagram spec rather than node and edge props. This example passes a JSON-encoded spec string; the mounted component renders a connected Ingest-to-Publish diagram.
tsximport { GrafloriaDiagram } from '@grafloria/react';
const spec = JSON.stringify({
nodes: [
{ id: 'ingest', position: { x: 40, y: 80 }, data: { label: 'Ingest' } },
{ id: 'publish', position: { x: 280, y: 80 }, data: { label: 'Publish' } },
],
edges: [{ id: 'e1', source: 'ingest', target: 'publish' }],
});
export default function KitDiagram() {
return (
<div style={{ height: 440 }}>
<GrafloriaDiagram spec={spec} />
</div>
);
}
5. Add a comment panel
Set comments on the flow to enable its comment store, then pass that store to GrafloriaCommentPanel. CommentStore is available from the instance after mount; this example anchors a starter thread to the Review node.
tsximport { useState } from 'react';
import { GrafloriaCommentPanel, GrafloriaFlow } from '@grafloria/react';
import type { CommentStore } from '@grafloria/engine';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{ id: 'design', position: { x: 40, y: 100 }, size: { width: 150, height: 66 }, label: 'Design' },
{ id: 'review', position: { x: 260, y: 100 }, size: { width: 150, height: 66 }, label: 'Review' },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'design', target: 'review' }];
export default function FlowWithComments() {
const [store, setStore] = useState<CommentStore | null>(null);
return (
<div style={{ display: 'flex', height: 440 }}>
<GrafloriaFlow
defaultNodes={nodes}
defaultEdges={edges}
comments
style={{ flex: 1, minWidth: 0 }}
onInit={(instance) => {
const commentStore = instance.getCommentStore();
if (commentStore) {
commentStore.createThread(
{ kind: 'node', id: 'review' },
'Can we tighten the review step?'
);
setStore(commentStore);
}
}}
/>
{store && (
<div style={{ width: 300, overflow: 'auto' }}>
<GrafloriaCommentPanel store={store} />
</div>
)}
</div>
);
}
The canvas renders Design and Review with their edge, and the panel appears beside it with a thread attached to Review. The panel follows the store passed at mount; this example keeps that store in React state until the component unmounts.
What you have
You now have an interactive React canvas, controlled state that stays synchronized with edits, sibling UI that reaches the canvas instance, a generic kit-diagram host, and a comment panel bound to the canvas store.
Where next
- Every demo as a React component — see the live React examples.
- How Grafloria works — the model and engine ideas behind the bindings.
- The instance and data flow — follow data between React and the live diagram.
- Custom nodes in React — render React components for node types.
Was this page helpful?