mcpField
R
react-flow-implementation

react-flow-implementation

github.com/existential-birds開発85364 回インストール11 週間前に更新
リポジトリ

react-flow-implementation について

react-flow-implementationは、React Flowを使ったUI実装のためのスキルです。スタイル、Providerの境界、安定したノードとエッジの型、ハンドル、状態管理、接続に関する実装パターンと順序付きチェックを提供します。

主な機能

  1. 01React Flowのセットアップパターン
  2. 02スタイルとProviderの確認
  3. 03安定したノード・エッジ型
  4. 04ハンドルとエッジの対応
  5. 05状態管理と接続パターン

活用シーン

  1. 01制御型ノード・エディターの構築
  2. 02カスタムノードやエッジの追加
  3. 03Providerとハンドル設定の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/existential-birds/beagle --skill react-flow-implementation

インストール後、Claude Code で/react-flow-implementationを入力して読み込まれたか確認します。

同梱ファイルADDITIONAL_COMPONENTS.mdEDGE_PATHS.md

詳細

React Flow の実装

クイックスタート

import { useCallback } from 'react';
import { ReactFlow, useNodesState, useEdgesState, addEdge } from '@xyflow/react';
import '@xyflow/react/dist/style.css';

const initialNodes = [
  { id: '1', position: { x: 0, y: 0 }, data: { label: 'Node 1' } },
  { id: '2', position: { x: 200, y: 100 }, data: { label: 'Node 2' } },
];

const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];

export default function Flow() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges]
  );

  return (
    <div style={{ width: '100%', height: '100vh' }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        fitView
      />
    </div>
  );
}

実装の確認項目

以下を順番に確認してください。「問題なさそう」という理由で先に進まないでください。

  1. ページ上のスタイル — 合格条件: <ReactFlow /> を描画するバンドルで @xyflow/react/dist/style.css(または同等の CSS パイプライン)をインポートしていること。これにより、ノードとエッジが表示され、ヒット対象の範囲が見た目と一致します。
  2. useReactFlow の境界 — 合格条件: useReactFlow() を呼び出すすべての箇所が、<ReactFlow /> と同じツリーをラップする <ReactFlowProvider> の子孫であること(または、意図的にストアを分割しており、両方のルートを特定できること)。
  3. 安定した nodeTypes/edgeTypes — 合格条件: これらのマップがモジュールスコープの定数か、依存値が安定した useMemo で定義されていること。<ReactFlow /> を描画するコンポーネント内で、レンダリングのたびに新しい { ... } リテラルを作成してはいけません。
  4. ハンドルとエッジの一致 — 合格条件: 複数の Handle id を持つノードで、特定のハンドルに接続する必要があるすべてのエッジに sourceHandle/targetHandle が設定されていること(または、デフォルトのハンドル解決を意図的に受け入れていること)。

MiniMap、Controls、Background、NodeToolbar、NodeResizer のパターンについては、ADDITIONAL_COMPONENTS.md を参照してください。

コアパターン

TypeScript の型
import type { Node, Edge, NodeProps, BuiltInNode } from '@xyflow/react';

// Define custom node type with data shape
type CustomNode = Node<{ value: number; label: string }, 'custom'>;

// Combine with built-in nodes
type MyNode = CustomNode | BuiltInNode;
type MyEdge = Edge<{ weight?: number }>;

// Use throughout app
const [nodes, setNodes] = useNodesState<MyNode>(initialNodes);
カスタムノード
import { memo } from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';

// Define node type
type CounterNode = Node<{ count: number }, 'counter'>;

// Always wrap in memo for performance
const CounterNode = memo(function CounterNode({ data, isConnectable }: NodeProps<CounterNode>) {
  return (
    <>
      <Handle type="target" position={Position.Top} isConnectable={isConnectable} />
      <div className="counter-node">
        Count: {data.count}
        {/* nodrag prevents dragging when interacting with button */}
        <button className="nodrag" onClick={() => console.log('clicked')}>
          Increment
        </button>
      </div>
      <Handle type="source" position={Position.Bottom} isConnectable={isConnectable} />
    </>
  );
});

// Register in nodeTypes (define OUTSIDE component to avoid re-renders)
const nodeTypes = { counter: CounterNode };

// Use in ReactFlow
<ReactFlow nodeTypes={nodeTypes} ... />
複数のハンドル
// Use handle IDs when a node has multiple handles of same type
<Handle type="source" position={Position.Right} id="a" />
<Handle type="source" position={Position.Right} id="b" style={{ top: 20 }} />

// Connect with specific handles
const edge = {
  id: 'e1-2',
  source: '1',
  sourceHandle: 'a',
  target: '2',
  targetHandle: null
};
カスタムエッジ
import { BaseEdge, EdgeProps, getSmoothStepPath } from '@xyflow/react';

function CustomEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data }: EdgeProps) {
  const [edgePath, labelX, labelY] = getSmoothStepPath({
    sourceX, sourceY, sourcePosition,
    targetX, targetY, targetPosition,
  });

  return (
    <>
      <BaseEdge id={id} path={edgePath} />
      <text x={labelX} y={labelY} className="edge-label">{data?.label}</text>
    </>
  );
}

const edgeTypes = { custom: CustomEdge };

状態管理

制御モード(本番環境向けの推奨方式)
// External state with change handlers
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);

const onNodesChange = useCallback(
  (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
  []
);

const onEdgesChange = useCallback(
  (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
  []
);

<ReactFlow
  nodes={nodes}
  edges={edges}
  onNodesChange={onNodesChange}
  onEdgesChange={onEdgesChange}
/>
useReactFlow の使用
import { useReactFlow, ReactFlowProvider } from '@xyflow/react';

function FlowControls() {
  const {
    getNodes, setNodes, addNodes, updateNodeData,
    getEdges, setEdges, addEdges,
    fitView, zoomIn, zoomOut, setViewport,
    deleteElements, toObject,
  } = useReactFlow();

  const addNode = () => {
    addNodes({ id: `${Date.now()}`, position: { x: 100, y: 100 }, data: { label: 'New' } });
  };

  return <button onClick={addNode}>Add Node</button>;
}

// Must wrap in provider when using useReactFlow
function App() {
  return (
    <ReactFlowProvider>
      <Flow />
      <FlowControls />
    </ReactFlowProvider>
  );
}
ノードデータの更新
const { updateNodeData } = useReactFlow();

// Merge with existing data
updateNodeData(nodeId, { label: 'Updated' });

// Replace data entirely
updateNodeData(nodeId, { newField: 'value' }, { replace: true });

ビューポートとビューに合わせる

// Fit on initial render
<ReactFlow fitView fitViewOptions={{ padding: 0.2, maxZoom: 1 }} />

// Programmatic control
const { fitView, setViewport, getViewport, zoomTo } = useReactFlow();

// Fit to specific nodes
fitView({ nodes: [{ id: '1' }, { id: '2' }], duration: 500 });

// Set exact viewport
setViewport({ x: 100, y: 100, zoom: 1.5 }, { duration: 300 });

接続の検証

const isValidConnection = useCallback((connection: Connection) => {
  // Prevent self-connections
  if (connection.source === connection.target) return false;

  // Custom validation logic
  const sourceNode = getNode(connection.source);
  const targetNode = getNode(connection.target);

  return sourceNode?.type !== targetNode?.type;
}, []);

<ReactFlow isValidConnection={isValidConnection} />

主な Props のリファレンス

<ReactFlow
  // Core data
  nodes={nodes}
  edges={edges}
  onNodesChange={onNodesChange}
  onEdgesChange={onEdgesChange}

  // Custom types (define OUTSIDE component)
  nodeTypes={nodeTypes}
  edgeTypes={edgeTypes}

  // Connections
  onConnect={onConnect}
  connectionMode={ConnectionMode.Loose}  // Allow target-to-target
  isValidConnection={isValidConnection}

  // Viewport
  fitView
  minZoom={0.1}
  maxZoom={4}
  defaultViewport={{ x: 0, y: 0, zoom: 1 }}

  // Interaction
  nodesDraggable={true}
  nodesConnectable={true}
  elementsSelectable={true}
  panOnDrag={true}
  zoomOnScroll={true}

  // Additional components
  <MiniMap />
  <Controls />
  <Background variant={BackgroundVariant.Dots} />
</ReactFlow>

インタラクション用 CSS クラス

クラス効果
nodrag要素をクリックしたときにドラッグを防止
nowheelホイール操作によるズームを防止
nopan要素からのパンを防止
nokeyキーボードイベントを防止(入力欄で使用)

追加コンポーネント

MiniMap、Controls、Background、NodeToolbar、NodeResizer については、ADDITIONAL_COMPONENTS.md を参照してください。

コメント