diff --git a/examples/react/src/examples/MultiSetNodes/index.tsx b/examples/react/src/examples/MultiSetNodes/index.tsx index fcb49924..b85cb33c 100644 --- a/examples/react/src/examples/MultiSetNodes/index.tsx +++ b/examples/react/src/examples/MultiSetNodes/index.tsx @@ -28,10 +28,15 @@ for (let i = 0; i < 100; i++) { }); } -const initEdges: Edge[] = []; +const initEdges: Edge[] = initNodes.reduce((res, node, index) => { + if (index > 0) { + res.push({ id: `${index - 1}-${index}`, source: (index - 1).toString(), target: index.toString() }); + } + return res; +}, []); const CustomNodeFlow = () => { - const { setNodes, updateNodeData } = useReactFlow(); + const { setNodes, updateNodeData, updateEdge } = useReactFlow(); const [nodes, , onNodesChange] = useNodesState(initNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges); @@ -55,6 +60,10 @@ const CustomNodeFlow = () => { nodes.forEach((node) => updateNodeData(node.id, { label: 'node update' })); }; + const multiUpdateEdges = () => { + edges.forEach((edge) => updateEdge(edge.id, { label: 'edge update' })); + }; + return ( { + ); diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 4f1404a4..6766e775 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -13,7 +13,7 @@ import { import useViewportHelper from './useViewportHelper'; import { useStore, useStoreApi } from './useStore'; import { useBatchContext } from '../components/BatchProvider'; -import { elementToRemoveChange, isNode } from '../utils'; +import { elementToRemoveChange, isEdge, isNode } from '../utils'; import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, GeneralHelpers } from '../types'; const selector = (s: ReactFlowState) => !!s.panZoom; @@ -41,6 +41,10 @@ export function useReactFlow['setEdges'] = (payload) => { + batchContext.edgeQueue.push(payload as EdgeType[]); + }; + const getNodeRect = (node: NodeType | { id: string }): Rect | null => { const { nodeLookup, nodeOrigin } = store.getState(); @@ -77,6 +81,23 @@ export function useReactFlow['updateEdge'] = ( + id, + edgeUpdate, + options = { replace: false } + ) => { + setEdges((prevEdges) => + prevEdges.map((edge) => { + if (edge.id === id) { + const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge as EdgeType) : edgeUpdate; + return options.replace && isEdge(nextEdge) ? (nextEdge as EdgeType) : { ...edge, ...nextEdge }; + } + + return edge; + }) + ); + }; + return { getNodes: () => store.getState().nodes.map((n) => ({ ...n })) as NodeType[], getNode: (id) => getInternalNode(id)?.internals.userNode as NodeType, @@ -87,9 +108,7 @@ export function useReactFlow store.getState().edgeLookup.get(id) as EdgeType, setNodes, - setEdges: (payload) => { - batchContext.edgeQueue.push(payload as EdgeType[]); - }, + setEdges, addNodes: (payload) => { const newNodes = Array.isArray(payload) ? payload : [payload]; batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]); @@ -200,6 +219,17 @@ export function useReactFlow { + updateEdge( + id, + (edge) => { + const nextData = typeof dataUpdate === 'function' ? dataUpdate(edge) : dataUpdate; + return options.replace ? { ...edge, data: nextData } : { ...edge, data: { ...edge.data, ...nextData } }; + }, + options + ); + }, }; }, []); diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index bd7c1d1b..5f4aa3b3 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -143,6 +143,36 @@ export type GeneralHelpers | ((node: NodeType) => Partial), options?: { replace: boolean } ) => void; + /** + * Updates an edge. + * + * @param id - id of the edge to update + * @param edgeUpdate - the edge update as an object or a function that receives the current edge and returns the edge update + * @param options.replace - if true, the edge is replaced with the edge update, otherwise the changes get merged + * + * @example + * updateEdge('edge-1', (edge) => ({ label: 'A new label' })); + */ + updateEdge: ( + id: string, + edgeUpdate: Partial | ((edge: EdgeType) => Partial), + options?: { replace: boolean } + ) => void; + /** + * Updates the data attribute of a edge. + * + * @param id - id of the edge to update + * @param dataUpdate - the data update as an object or a function that receives the current data and returns the data update + * @param options.replace - if true, the data is replaced with the data update, otherwise the changes get merged + * + * @example + * updateEdgeData('edge-1', { label: 'A new label' }); + */ + updateEdgeData: ( + id: string, + dataUpdate: Partial | ((edge: EdgeType) => Partial), + options?: { replace: boolean } + ) => void; }; export type ReactFlowInstance = GeneralHelpers<