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
相关产品推荐
相关产品推荐

