← 返回 Skills 市场
anderskev

React Flow Advanced

作者 Kevin Anderson · GitHub ↗ · v1.1.1 · MIT-0
cross-platform ✓ 安全检测通过
164
总下载
0
收藏
1
当前安装
2
版本数
在 OpenClaw 中安装
/install react-flow-advanced
功能描述
Advanced React Flow patterns for complex use cases. Use when implementing sub-flows, custom connection lines, programmatic layouts, drag-and-drop, undo/redo,...
使用说明 (SKILL.md)

Advanced React Flow Patterns

Gates (check before shipping)

Use these as sequenced checks—not “I think it works.”

  1. Sub-flows / groups: Pass: Every parentId matches an existing node id; the parent type is registered in nodeTypes; child positions are relative to the parent as intended (spot-check one drag inside/outside the group).
  2. Custom connection line: Pass: With a valid/invalid drag, stroke or connectionStatus visibly differs; path renders without console errors from getSmoothStepPath (invalid coords).
  3. External drag-and-drop: Pass: onDragOver always preventDefault(); drop position uses screenToFlowPosition (not raw clientX/clientY as flow coords); new node appears under the cursor on the pane.
  4. Undo/redo: Pass: One undo returns to the prior { nodes, edges }; redo restores; rapid changes do not leave canUndo/canRedo inconsistent with visible graph (exercise add → undo → redo once).
  5. Programmatic layout (dagre): Pass: After setNodes, node positions match intended rankdir; fitView runs after layout (e.g. requestAnimationFrame) so the viewport is not stale.
  6. Connect on drop (new node): Pass: Dropping on empty pane creates a node and an edge from the source handle; dropping on a valid target does not duplicate nodes (only the invalid-drop path adds a node).
  7. Selectors / store: Pass: Components that useStore with objects use shallow (or equivalent) so unrelated store updates do not re-render every frame.

Sub-Flows (Nested Nodes)

const nodes = [
  // Parent (group) node
  {
    id: 'group-1',
    type: 'group',
    position: { x: 0, y: 0 },
    style: { width: 400, height: 300, padding: 10 },
    data: { label: 'Group' },
  },
  // Child nodes
  {
    id: 'child-1',
    parentId: 'group-1',        // Reference parent
    extent: 'parent',           // Constrain to parent bounds
    expandParent: true,         // Auto-expand parent if dragged to edge
    position: { x: 20, y: 50 }, // Relative to parent
    data: { label: 'Child 1' },
  },
  {
    id: 'child-2',
    parentId: 'group-1',
    extent: 'parent',
    position: { x: 200, y: 50 },
    data: { label: 'Child 2' },
  },
];

Group Node Component

function GroupNode({ data, id }: NodeProps) {
  return (
    \x3Cdiv className="group-node">
      \x3Cdiv className="group-header">{data.label}\x3C/div>
      {/* Children are rendered automatically by React Flow */}
    \x3C/div>
  );
}

Custom Connection Line

import { ConnectionLineComponentProps, getSmoothStepPath } from '@xyflow/react';

function CustomConnectionLine({
  fromX, fromY, fromPosition,
  toX, toY, toPosition,
  connectionStatus,
}: ConnectionLineComponentProps) {
  const [path] = getSmoothStepPath({
    sourceX: fromX,
    sourceY: fromY,
    sourcePosition: fromPosition,
    targetX: toX,
    targetY: toY,
    targetPosition: toPosition,
  });

  return (
    \x3Cg>
      \x3Cpath
        d={path}
        fill="none"
        stroke={connectionStatus === 'valid' ? '#22c55e' : '#ef4444'}
        strokeWidth={2}
        strokeDasharray="5 5"
      />
    \x3C/g>
  );
}

\x3CReactFlow connectionLineComponent={CustomConnectionLine} />

Drag and Drop from External Source

import { useCallback, useRef, useState } from 'react';
import { useReactFlow } from '@xyflow/react';

function DnDFlow() {
  const reactFlowWrapper = useRef(null);
  const { screenToFlowPosition, addNodes } = useReactFlow();
  const [reactFlowInstance, setReactFlowInstance] = useState(null);

  const onDragOver = useCallback((event: DragEvent) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  const onDrop = useCallback((event: DragEvent) => {
    event.preventDefault();

    const type = event.dataTransfer.getData('application/reactflow');
    if (!type) return;

    // Convert screen position to flow position
    const position = screenToFlowPosition({
      x: event.clientX,
      y: event.clientY,
    });

    const newNode = {
      id: `${Date.now()}`,
      type,
      position,
      data: { label: `${type} node` },
    };

    addNodes(newNode);
  }, [screenToFlowPosition, addNodes]);

  return (
    \x3Cdiv ref={reactFlowWrapper} style={{ height: '100%' }}>
      \x3CReactFlow
        onDragOver={onDragOver}
        onDrop={onDrop}
        onInit={setReactFlowInstance}
      />
    \x3C/div>
  );
}

// Sidebar component
function Sidebar() {
  const onDragStart = (event: DragEvent, nodeType: string) => {
    event.dataTransfer.setData('application/reactflow', nodeType);
    event.dataTransfer.effectAllowed = 'move';
  };

  return (
    \x3Caside>
      \x3Cdiv draggable onDragStart={(e) => onDragStart(e, 'input')}>
        Input Node
      \x3C/div>
      \x3Cdiv draggable onDragStart={(e) => onDragStart(e, 'default')}>
        Default Node
      \x3C/div>
    \x3C/aside>
  );
}

Undo/Redo

import { useCallback, useState } from 'react';

function useUndoRedo\x3CT>(initialState: T) {
  const [history, setHistory] = useState\x3CT[]>([initialState]);
  const [index, setIndex] = useState(0);

  const state = history[index];

  const setState = useCallback((newState: T | ((prev: T) => T)) => {
    setHistory((prev) => {
      const resolved = typeof newState === 'function'
        ? (newState as (prev: T) => T)(prev[index])
        : newState;

      // Remove future states and add new state
      const newHistory = prev.slice(0, index + 1);
      return [...newHistory, resolved];
    });
    setIndex((i) => i + 1);
  }, [index]);

  const undo = useCallback(() => {
    setIndex((i) => Math.max(0, i - 1));
  }, []);

  const redo = useCallback(() => {
    setIndex((i) => Math.min(history.length - 1, i + 1));
  }, [history.length]);

  const canUndo = index > 0;
  const canRedo = index \x3C history.length - 1;

  return { state, setState, undo, redo, canUndo, canRedo };
}

// Usage
function Flow() {
  const {
    state: { nodes, edges },
    setState,
    undo, redo, canUndo, canRedo
  } = useUndoRedo({ nodes: initialNodes, edges: initialEdges });

  // Capture state on significant changes
  const onNodesChange = useCallback((changes) => {
    const hasPositionChange = changes.some(c => c.type === 'position' && !c.dragging);
    if (hasPositionChange) {
      setState(prev => ({
        nodes: applyNodeChanges(changes, prev.nodes),
        edges: prev.edges,
      }));
    }
  }, [setState]);
}

Programmatic Layout with dagre

import dagre from 'dagre';

interface LayoutOptions {
  direction: 'TB' | 'BT' | 'LR' | 'RL';
  nodeWidth: number;
  nodeHeight: number;
}

function getLayoutedElements(
  nodes: Node[],
  edges: Edge[],
  options: LayoutOptions = { direction: 'TB', nodeWidth: 172, nodeHeight: 36 }
) {
  const g = new dagre.graphlib.Graph();
  g.setGraph({ rankdir: options.direction });
  g.setDefaultEdgeLabel(() => ({}));

  nodes.forEach((node) => {
    g.setNode(node.id, {
      width: node.measured?.width ?? options.nodeWidth,
      height: node.measured?.height ?? options.nodeHeight,
    });
  });

  edges.forEach((edge) => {
    g.setEdge(edge.source, edge.target);
  });

  dagre.layout(g);

  const layoutedNodes = nodes.map((node) => {
    const nodeWithPosition = g.node(node.id);
    return {
      ...node,
      position: {
        x: nodeWithPosition.x - (node.measured?.width ?? options.nodeWidth) / 2,
        y: nodeWithPosition.y - (node.measured?.height ?? options.nodeHeight) / 2,
      },
    };
  });

  return { nodes: layoutedNodes, edges };
}

// Usage after nodes are measured
function Flow() {
  const { fitView } = useReactFlow();

  const onLayout = useCallback((direction: 'TB' | 'LR') => {
    const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
      nodes,
      edges,
      { direction, nodeWidth: 150, nodeHeight: 50 }
    );

    setNodes([...layoutedNodes]);
    setEdges([...layoutedEdges]);

    window.requestAnimationFrame(() => {
      fitView({ duration: 500 });
    });
  }, [nodes, edges, setNodes, setEdges, fitView]);
}

Connection with Edge on Drop

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

  const onConnectEnd = useCallback(
    (event: MouseEvent | TouchEvent, connectionState: FinalConnectionState) => {
      // Only proceed if dropped on pane (not on a node)
      if (!connectionState.isValid && connectionState.fromHandle) {
        const id = `${Date.now()}`;
        const { clientX, clientY } = 'changedTouches' in event
          ? event.changedTouches[0]
          : event;

        const newNode = {
          id,
          position: screenToFlowPosition({ x: clientX, y: clientY }),
          data: { label: 'New Node' },
        };

        setNodes((nds) => [...nds, newNode]);
        setEdges((eds) => [
          ...eds,
          {
            id: `e-${connectionState.fromNode?.id}-${id}`,
            source: connectionState.fromNode?.id ?? '',
            target: id,
          },
        ]);
      }
    },
    [screenToFlowPosition, setNodes, setEdges]
  );

  return (
    \x3CReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnectEnd={onConnectEnd}
    />
  );
}

Accessing Node Data from Edges

import { useNodesData, type EdgeProps } from '@xyflow/react';

function DataEdge({ source, target, ...props }: EdgeProps) {
  // Get data for source and target nodes
  const nodesData = useNodesData([source, target]);
  const sourceData = nodesData[0];
  const targetData = nodesData[1];

  const [path, labelX, labelY] = getSmoothStepPath(props);

  return (
    \x3C>
      \x3CBaseEdge path={path} />
      \x3CEdgeLabelRenderer>
        \x3Cdiv style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}>
          {sourceData?.data?.label} → {targetData?.data?.label}
        \x3C/div>
      \x3C/EdgeLabelRenderer>
    \x3C/>
  );
}

Middleware for Node Changes

// Filter or modify changes before they're applied
const onNodesChangeMiddleware = useCallback((changes: NodeChange[]) => {
  // Example: Prevent deletion of certain nodes
  const filteredChanges = changes.filter((change) => {
    if (change.type === 'remove') {
      const node = nodes.find((n) => n.id === change.id);
      return node?.data?.deletable !== false;
    }
    return true;
  });

  setNodes((nds) => applyNodeChanges(filteredChanges, nds));
}, [nodes, setNodes]);

Keyboard Shortcuts

import { useKeyPress } from '@xyflow/react';

function Flow() {
  const { deleteElements, getNodes, getEdges, fitView } = useReactFlow();

  // Ctrl/Cmd + A: Select all
  const selectAllPressed = useKeyPress(['Meta+a', 'Control+a']);

  useEffect(() => {
    if (selectAllPressed) {
      setNodes((nds) => nds.map((n) => ({ ...n, selected: true })));
      setEdges((eds) => eds.map((e) => ({ ...e, selected: true })));
    }
  }, [selectAllPressed]);

  // Custom delete handler
  const deletePressed = useKeyPress(['Backspace', 'Delete']);

  useEffect(() => {
    if (deletePressed) {
      const selectedNodes = getNodes().filter((n) => n.selected);
      const selectedEdges = getEdges().filter((e) => e.selected);
      deleteElements({ nodes: selectedNodes, edges: selectedEdges });
    }
  }, [deletePressed]);
}

Performance: Memoizing Selectors

import { useCallback } from 'react';
import { useStore, type ReactFlowState } from '@xyflow/react';
import { shallow } from 'zustand/shallow';

// Create stable selector outside component
const nodesSelector = (state: ReactFlowState) => state.nodes;

// Or use multiple values with shallow compare
const flowStateSelector = (state: ReactFlowState) => ({
  nodes: state.nodes,
  edges: state.edges,
  viewport: state.transform,
});

function FlowInfo() {
  const { nodes, edges, viewport } = useStore(flowStateSelector, shallow);
  return \x3Cdiv>Nodes: {nodes.length}, Edges: {edges.length}\x3C/div>;
}
安全使用建议
This skill is a set of code examples and QA gates for React Flow and appears coherent with its description. Before using: (1) verify the import/package names in your project (the examples import from '@xyflow/react' — confirm this matches the React Flow package you use), (2) review and test the snippets locally to ensure they match your React Flow version and coding standards, and (3) because the publisher/source is unknown and no homepage is provided, treat it as community-sourced documentation rather than an official library; prefer official documentation or packages if provenance matters to you.
功能分析
Type: OpenClaw Skill Name: react-flow-advanced Version: 1.1.1 The skill bundle provides legitimate React/TypeScript code snippets and implementation guidelines for advanced React Flow patterns, including sub-flows, drag-and-drop functionality, and programmatic layouts using the 'dagre' library. The content in SKILL.md is strictly technical and aligns with the stated purpose, showing no signs of malicious intent, data exfiltration, or unauthorized execution.
能力评估
Purpose & Capability
The name and description (advanced React Flow patterns) match the SKILL.md content: sample components, hooks, and runtime checks for React Flow behavior. There are no unrelated environment variables, binaries, or credentials requested.
Instruction Scope
The instructions are code examples and checklist gates for UI/behavioral tests. They do not instruct the agent to read system files, access environment variables, call external endpoints, or transmit data. All operations shown are client-side React patterns.
Install Mechanism
No install spec or code files are present (instruction-only), so nothing is written to disk or downloaded by the skill. Note: the skill source is unknown and there is no homepage, so you cannot verify author or provenance from this package metadata alone — but that is an author/source metadata issue, not an execution risk for the skill itself.
Credentials
The skill requests no environment variables, credentials, or config paths. The examples reference React Flow APIs only; there are no disproportionate or unrelated secrets requested.
Persistence & Privilege
The skill does not request persistent installation or elevated privileges; always is false and no special platform privileges are declared. As an instruction-only skill it has no ability to modify system or other skills' configs.
如何使用
  1. 确保已安装 OpenClaw(本地或 Docker 部署)
  2. 在对话框中输入安装命令:/install react-flow-advanced
  3. 安装完成后,直接呼叫该 Skill 的名称或使用 /react-flow-advanced 触发
  4. 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.1.1
**Adds a checklist of gate/test criteria for all advanced React Flow patterns.** - Introduced a "Gates (check before shipping)" section detailing pass/fail criteria for core patterns (sub-flows, custom connection lines, drag-and-drop, undo/redo, layouts, connect-on-drop, selectors). - No changes to code, just improved documentation with explicit validations for each advanced use case. - Helps ensure correct implementation and prevents common mistakes in usage.
v1.1.0
react-flow-advanced 1.1.0 - New documentation with advanced usage patterns, including: - Sub-flows (nested/group nodes) - Custom connection line components - External drag-and-drop node creation - Undo/redo state management example - Programmatic layout with Dagre integration - Creating nodes and edges via edge drop (connection edge on drop)
元数据
Slug react-flow-advanced
版本 1.1.1
许可证 MIT-0
累计安装 1
当前安装数 1
历史版本数 2
常见问题

React Flow Advanced 是什么?

Advanced React Flow patterns for complex use cases. Use when implementing sub-flows, custom connection lines, programmatic layouts, drag-and-drop, undo/redo,... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 164 次。

如何安装 React Flow Advanced?

在 OpenClaw 或 Claude Code 对话框中运行命令「/install react-flow-advanced」即可一键安装,无需额外配置。

React Flow Advanced 是免费的吗?

是的,React Flow Advanced 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。

React Flow Advanced 支持哪些平台?

React Flow Advanced 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。

谁开发了 React Flow Advanced?

由 Kevin Anderson(@anderskev)开发并维护,当前版本 v1.1.1。

💬 留言讨论