React Flow v12 を最小コードで動かす
@xyflow/react v12 の最小サンプル + カスタムノード + コンテキストメニュー + 永続化までを、つまずいたポイントとともに記録した実装メモ。
検証日: 2026-05-09
使用バージョン:
@xyflow/react@12.10.1想定読者: React + TypeScript を書ける、ノードベース UI ライブラリを評価中の人
ワークフローエディタやマインドマップのような ノードベース UI を React で実装する場合、
2026 年時点での主要選択肢は React Flow(@xyflow/react) です。
旧称 reactflow から @xyflow/react に移行しており、検索時に古い情報を掴みやすいので注意します。
この記事では、最小サンプルから「カスタムノード + 右クリックメニュー + ローカル永続化」までを段階的に組みます。
1. 最小サンプル
<ReactFlow nodes={...} edges={...}> を <ReactFlowProvider> で囲み、useNodesState / useEdgesState で state を扱う:
import {
ReactFlow,
ReactFlowProvider,
useNodesState,
useEdgesState,
Controls,
Background,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
const initialNodes = [
{ id: "1", position: { x: 0, y: 0 }, data: { label: "入力" } },
{ id: "2", position: { x: 200, y: 100 }, data: { label: "処理" } },
];
function WorkflowBuilder() {
const [nodes, , onNodesChange] = useNodesState(initialNodes);
const [edges, , onEdgesChange] = useEdgesState([]);
return (
<div style={{ width: "100vw", height: "100vh" }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
);
}
export default function App() {
return (
<ReactFlowProvider>
<WorkflowBuilder />
</ReactFlowProvider>
);
}
2. カスタムノードの登録
独自 UI のノードは Handle で接続点を定義し、nodeTypes に名前で登録します。
import { Handle, Position } from "@xyflow/react";
export function CustomNode({ data }: { data: { label: string } }) {
return (
<div className="p-2 border rounded bg-white">
<strong>{data.label}</strong>
<Handle type="source" position={Position.Bottom} />
<Handle type="target" position={Position.Top} />
</div>
);
}
const nodeTypes = { custom: CustomNode };
// <ReactFlow nodeTypes={nodeTypes} ... />
3. 右クリックメニュー
ノード / ペインの onNodeContextMenu / onPaneContextMenu を抑制してカスタムメニューを表示します。
const onNodeContextMenu = useCallback((event: React.MouseEvent, node: Node) => {
event.preventDefault();
setMenu({ id: node.id, top: event.clientY, left: event.clientX });
}, []);
4. ローカル永続化
useReactFlow().toObject() でビューポート込みの状態を JSON 化できます。
const { toObject, setViewport } = useReactFlow();
const onSave = useCallback(() => {
localStorage.setItem("my-flow", JSON.stringify(toObject()));
}, [toObject]);
const onRestore = useCallback(() => {
const raw = localStorage.getItem("my-flow");
if (!raw) return;
const flow = JSON.parse(raw);
setNodes(flow.nodes ?? []);
setEdges(flow.edges ?? []);
setViewport(flow.viewport);
}, [setNodes, setEdges, setViewport]);
つまずいたポイント
dataを mutate すると再描画されない。新しいオブジェクトを作って参照を切る必要がある({ ...prev, label: "..." })。- node id は string 必須。
crypto.randomUUID()などで明示的に文字列化する。 - 古い記事は
reactflowパッケージを使っていることが多い。v11 以前の API は別物なので、検索時は「@xyflow/react」で絞る。 - TypeScript の型は
Node/Edgeを generic で受ける。dataに独自型を入れる時はNode<MyData>を意識する。
評価
| 観点 | 評価 | コメント |
|---|---|---|
| 学習コスト | ◎ | 公式ドキュメントの最小サンプルがそのまま動く |
| 型サポート | ○ | generic で data の型を絞り込める。zustand 統合時はやや煩雑 |
| パフォーマンス | ◯ | 数百ノードまでは余裕、数千になると仮想化の検討必要 |
| エコシステム | ◯ | @xyflow/react 移行直後の記事はまだ少ない、公式 example が一次資料 |
向く / 向かないケース
- 向く: ワークフローエディタ、ER 図、マインドマップ、データフロー UI、設定 UI
- 向かない: 1 万ノード級の大規模可視化(別ライブラリ検討)、純粋な静的図(Mermaid 等で十分)
関連 Topic / 関連書籍
この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:
React Native+Expoではじめるスマホアプリ開発 : JavaScriptによるアプリ構築の実際
「React Native」は、Facebookが開発しているスマートフォンアプリ向けの開発環境です。ほとんどのコードをJav…
Reactハンズオンラーニング 第2版 : Webアプリケーション開発のベストプラクティス
Webフロントエンドの「今」を学びたい人へ! Facebookが開発したJavaScrip…
Reactビギナーズガイド : コンポーネントベースのフロントエンド開発入門
FacebookのエンジニアによるReactの入門書! ReactによるコンポーネントベースのWebフロントエンド開発の…
TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発
新しいフロントエンドの入門書決定版! 本書はReact/Next.jsとTypeScriptを用いてWebアプリケーションを開…