You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Flow自定义节点按钮Handle重排后位置异常问题

问题描述

使用React Flow + Framer Motion构建流程编辑器时,自定义节点内的带Handle按钮在添加或重排后出现异常:按钮显示顺序正常,但点击按钮的Handle创建连线时,连线起点仍停留在该按钮重排前的位置。已调用useUpdateNodeInternals通知React Flow节点内部更新,但问题未解决。

相关代码

自定义节点 genericTemplateNode.tsx

import React, { Dispatch, SetStateAction, useCallback, useState } from "react";
import { Handle, Position, useUpdateNodeInternals } from "@xyflow/react";
import { Reorder } from "framer-motion";
import ActionButton from "../ui/genericTemplateUtils/actionButtons";
import { Button } from "./Interface/NodesInterface";

const GenericTemplateNode = ({ id, data }: { id: string, data: { setNodes: Dispatch<SetStateAction<Node[]>>, buttons: Button[] } }) => {
  const [idbutton, setIdbutton] = useState<number>(1);
  const updateNodeInternals = useUpdateNodeInternals();

  // Function to update buttons
  const updateButtons = useCallback((_buttons: Button[]) => {
    data.setNodes((nds) =>
      nds.map((node) => {
        if (node.id === id) {
          return {
            ...node,
            data: { ...node.data, buttons: _buttons }
          };
        }
        return node;
      })
    );
    updateNodeInternals(id); // Notify React Flow of changes
  }, [id, data.setNodes]);

  return (
    <div>
      <Reorder.Group
        axis="y"
        values={data.buttons}
        onReorder={(newButtons) => updateButtons(newButtons)}
      >
        {data.buttons.map((button) => (
          <ActionButton key={button.id} button={button} />
        ))}
      </Reorder.Group>
      <Handle position={Position.Left} type="target" />
    </div>
  );
};

export default GenericTemplateNode;

react-flow.tsx

import React, { useCallback, useState } from "react";
import { ReactFlow, applyNodeChanges, Node } from "@xyflow/react";
import GenericTemplateNode from "@/components/GenericTemplateNode";

const initialNodes: Node[] = [];

export default function FlowBuilder() {
  const [nodes, setNodes] = useState<Node[]>(initialNodes);

  const addNode = () => {
    const newNode = {
      id: (nodes.length + 1).toString(),
      type: 'genericTemplate',
      position: { x: Math.random() * 250, y: Math.random() * 250 },
      data: { setNodes, buttons: [] },
    };
    setNodes((nds) => [...nds, newNode]);
  };

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

  return (
    <div>
      <button onClick={addNode}>Add Node</button>
      <ReactFlow nodes={nodes} onNodesChange={onNodesChange} nodeTypes={{ genericTemplate: GenericTemplateNode }} />
    </div>
  );
}
解决方案

1. 给每个Handle添加唯一标识

React Flow通过Handle的id属性跟踪位置,需确保每个按钮的Handle有唯一标识。在ActionButton组件中为Handle添加id:

// ActionButton.tsx示例代码
import { Handle, Position } from "@xyflow/react";

const ActionButton = ({ button, nodeId }) => {
  return (
    <div>
      <Handle
        type="source"
        position={Position.Right}
        id={`${nodeId}-${button.id}`} // 生成唯一标识
      />
      {/* 按钮其他内容 */}
    </div>
  );
};

同时在GenericTemplateNode中传递节点ID给ActionButton:

{data.buttons.map((button) => (
  <ActionButton key={button.id} button={button} nodeId={id} />
))}

2. 调整updateNodeInternals调用时机

setNodes是异步更新状态,直接在其后调用updateNodeInternals时DOM尚未重渲染,React Flow无法获取最新位置。改用useLayoutEffect监听按钮列表变化,在DOM更新后同步触发更新:

import { useLayoutEffect } from "react";

// 在GenericTemplateNode组件内添加
useLayoutEffect(() => {
  updateNodeInternals(id);
}, [data.buttons, id, updateNodeInternals]);

// 移除updateButtons中的updateNodeInternals调用
const updateButtons = useCallback((_buttons: Button[]) => {
  data.setNodes((nds) =>
    nds.map((node) => {
      if (node.id === id) {
        return {
          ...node,
          data: { ...node.data, buttons: _buttons }
        };
      }
      return node;
    })
  );
}, [id, data.setNodes]);

3. 处理Framer Motion动画的影响

Framer Motion的重排动画可能导致React Flow读取到中间状态的位置。如果需要保留动画,可在动画结束后触发更新:

<Reorder.Group
  axis="y"
  values={data.buttons}
  onReorder={(newButtons) => updateButtons(newButtons)}
  onAnimationComplete={() => updateNodeInternals(id)}
>
  {/* 按钮列表 */}
</Reorder.Group>

内容的提问来源于stack exchange,提问作者Narendra Dewasi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:02:05