diff --git a/.changeset/happy-students-wink.md b/.changeset/happy-students-wink.md new file mode 100644 index 00000000..06ecf274 --- /dev/null +++ b/.changeset/happy-students-wink.md @@ -0,0 +1,7 @@ +--- +'@reactflow/core': patch +'@reactflow/minimap': patch +'@reactflow/node-toolbar': patch +--- + +use getNodes function diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx index f7a42270..05d893ca 100644 --- a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx @@ -1,5 +1,5 @@ import { FC, MouseEvent } from 'react'; -import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore, ReactFlowStore } from 'reactflow'; +import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore } from 'reactflow'; const CustomEdge: FC = ({ id, @@ -14,7 +14,7 @@ const CustomEdge: FC = ({ data, }) => { const isConnectedNodeDragging = useStore((s) => - Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) + s.getNodes().find((n) => n.dragging && (target === n.id || source === n.id)) ); const [edgePath, labelX, labelY] = getBezierPath({ diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx index e0e8e794..ebdb1641 100644 --- a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx @@ -14,7 +14,7 @@ const CustomEdge: FC = ({ data, }) => { const isConnectedNodeDragging = useStore((s) => - Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) + s.getNodes().find((n) => n.dragging && (target === n.id || source === n.id)) ); const [edgePath, labelX, labelY] = getBezierPath({ diff --git a/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx b/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx index 349db385..fa1697ae 100644 --- a/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/SelectedNodesToolbar.tsx @@ -1,7 +1,8 @@ import { NodeToolbar, ReactFlowState, useStore } from 'reactflow'; const selectedNodesSelector = (state: ReactFlowState) => - Array.from(state.nodeInternals.values()) + state + .getNodes() .filter((node) => node.selected) .map((node) => node.id); diff --git a/examples/vite-app/src/examples/Provider/Sidebar.tsx b/examples/vite-app/src/examples/Provider/Sidebar.tsx index 145ac718..a5205642 100644 --- a/examples/vite-app/src/examples/Provider/Sidebar.tsx +++ b/examples/vite-app/src/examples/Provider/Sidebar.tsx @@ -1,15 +1,16 @@ -import { useStore, useStoreApi } from 'reactflow'; +import { useReactFlow, useStore } from 'reactflow'; import styles from './provider.module.css'; const Sidebar = () => { - const store = useStoreApi(); - const nodeInternals = useStore((store) => store.nodeInternals); + const { setNodes } = useReactFlow(); + const nodeInfos = useStore((store) => + store.getNodes().map((n) => `Node ${n.id} - x: ${n.position.x.toFixed(2)}, y: ${n.position.y.toFixed(2)}`) + ); const transform = useStore((store) => store.transform); const selectAll = () => { - nodeInternals.forEach((node) => (node.selected = true)); - store.setState({ nodeInternals: new Map(nodeInternals) }); + setNodes((nodes) => nodes.map((n) => ({ ...n, selected: true }))); }; return ( @@ -22,10 +23,8 @@ const Sidebar = () => { [{transform[0].toFixed(2)}, {transform[1].toFixed(2)}, {transform[2].toFixed(2)}]
Nodes
- {Array.from(nodeInternals).map(([, node]) => ( -
- Node {node.id} - x: {node.position.x.toFixed(2)}, y: {node.position.y.toFixed(2)} -
+ {nodeInfos.map((info, index) => ( +
{info}
))}
diff --git a/packages/core/src/components/NodesSelection/index.tsx b/packages/core/src/components/NodesSelection/index.tsx index ed841db6..cff39b5d 100644 --- a/packages/core/src/components/NodesSelection/index.tsx +++ b/packages/core/src/components/NodesSelection/index.tsx @@ -27,7 +27,7 @@ const selector = (s: ReactFlowState) => ({ }); const bboxSelector = (s: ReactFlowState) => { - const selectedNodes = Array.from(s.nodeInternals.values()).filter((n) => n.selected); + const selectedNodes = s.getNodes().filter((n) => n.selected); return getRectOfNodes(selectedNodes, s.nodeOrigin); }; @@ -55,7 +55,10 @@ function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboar const onContextMenu = onSelectionContextMenu ? (event: MouseEvent) => { - const selectedNodes = Array.from(store.getState().nodeInternals.values()).filter((n) => n.selected); + const selectedNodes = store + .getState() + .getNodes() + .filter((n) => n.selected); onSelectionContextMenu(event, selectedNodes); } : undefined; diff --git a/packages/core/src/components/SelectionListener/index.tsx b/packages/core/src/components/SelectionListener/index.tsx index 6177f53d..14c104fb 100644 --- a/packages/core/src/components/SelectionListener/index.tsx +++ b/packages/core/src/components/SelectionListener/index.tsx @@ -9,7 +9,7 @@ type SelectionListenerProps = { }; const selector = (s: ReactFlowState) => ({ - selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected), + selectedNodes: s.getNodes().filter((n) => n.selected), selectedEdges: s.edges.filter((e) => e.selected), }); diff --git a/packages/core/src/components/UserSelection/index.tsx b/packages/core/src/components/UserSelection/index.tsx index cbf1e830..ddf8bd5f 100644 --- a/packages/core/src/components/UserSelection/index.tsx +++ b/packages/core/src/components/UserSelection/index.tsx @@ -101,13 +101,13 @@ const UserSelection = memo(({ selectionKeyPressed }: UserSelectionProps) => { height: Math.abs(mousePos.y - startY), }; - const { nodeInternals, edges, transform, onNodesChange, onEdgesChange, nodeOrigin } = store.getState(); - const nodes = Array.from(nodeInternals.values()); + const { nodeInternals, edges, transform, onNodesChange, onEdgesChange, nodeOrigin, getNodes } = store.getState(); const selectedNodes = getNodesInside(nodeInternals, nextUserSelectRect, transform, false, true, nodeOrigin); const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id); const selectedNodeIds = selectedNodes.map((n) => n.id); if (prevSelectedNodesCount.current !== selectedNodeIds.length) { + const nodes = getNodes(); prevSelectedNodesCount.current = selectedNodeIds.length; const changes = getSelectionChanges(nodes, selectedNodeIds) as NodeChange[]; if (changes.length) { diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts index f2b206e7..8665f6d0 100644 --- a/packages/core/src/hooks/useGlobalKeyHandler.ts +++ b/packages/core/src/hooks/useGlobalKeyHandler.ts @@ -18,11 +18,10 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { useEffect(() => { if (deleteKeyPressed) { - const { nodeInternals, edges } = store.getState(); - const nodes = Array.from(nodeInternals.values()); - const selectedNodes = nodes.filter((node) => node.selected); + const { edges, getNodes } = store.getState(); + const selectedNodes = getNodes().filter((node) => node.selected); const selectedEdges = edges.filter((edge) => edge.selected); - deleteElements({nodes: selectedNodes, edges: selectedEdges}); + deleteElements({ nodes: selectedNodes, edges: selectedEdges }); store.setState({ nodesSelectionActive: false }); } }, [deleteKeyPressed]); diff --git a/packages/core/src/hooks/useNodes.ts b/packages/core/src/hooks/useNodes.ts index 6a04942e..8681b4fd 100644 --- a/packages/core/src/hooks/useNodes.ts +++ b/packages/core/src/hooks/useNodes.ts @@ -1,7 +1,7 @@ import { useStore } from '../hooks/useStore'; import type { Node, ReactFlowState } from '../types'; -const nodesSelector = (state: ReactFlowState) => Array.from(state.nodeInternals.values()); +const nodesSelector = (state: ReactFlowState) => state.getNodes(); function useNodes(): Node[] { const nodes = useStore(nodesSelector); diff --git a/packages/core/src/hooks/useNodesInitialized.ts b/packages/core/src/hooks/useNodesInitialized.ts index f0ac25d2..45b14a33 100644 --- a/packages/core/src/hooks/useNodesInitialized.ts +++ b/packages/core/src/hooks/useNodesInitialized.ts @@ -7,7 +7,7 @@ const selector = (s: ReactFlowState) => { return false; } - return Array.from(s.nodeInternals.values()).every((n) => n[internalsSymbol]?.handleBounds !== undefined); + return s.getNodes().every((n) => n[internalsSymbol]?.handleBounds !== undefined); }; function useNodesInitialized(): boolean { diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index 0801a772..32666212 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -24,14 +24,14 @@ export default function useReactFlow(): ReactFlo const store = useStoreApi(); const getNodes = useCallback>(() => { - const { nodeInternals } = store.getState(); - const nodes = Array.from(nodeInternals.values()); - return nodes.map((n) => ({ ...n })); + return store + .getState() + .getNodes() + .map((n) => ({ ...n })); }, []); const getNode = useCallback>((id) => { - const { nodeInternals } = store.getState(); - return nodeInternals.get(id); + return store.getState().nodeInternals.get(id); }, []); const getEdges = useCallback>(() => { @@ -45,8 +45,8 @@ export default function useReactFlow(): ReactFlo }, []); const setNodes = useCallback>((payload) => { - const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); - const nodes = Array.from(nodeInternals.values()); + const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); + const nodes = getNodes(); const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; if (hasDefaultNodes) { @@ -77,10 +77,10 @@ export default function useReactFlow(): ReactFlo const addNodes = useCallback>((payload) => { const nodes = Array.isArray(payload) ? payload : [payload]; - const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); + const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); if (hasDefaultNodes) { - const currentNodes = Array.from(nodeInternals.values()); + const currentNodes = getNodes(); const nextNodes = [...currentNodes, ...nodes]; setNodes(nextNodes); } else if (onNodesChange) { @@ -102,11 +102,10 @@ export default function useReactFlow(): ReactFlo }, []); const toObject = useCallback>(() => { - const { nodeInternals, edges = [], transform } = store.getState(); - const nodes = Array.from(nodeInternals.values()); + const { getNodes, edges = [], transform } = store.getState(); const [x, y, zoom] = transform; return { - nodes: nodes.map((n) => ({ ...n })), + nodes: getNodes().map((n) => ({ ...n })), edges: edges.map((e) => ({ ...e })), viewport: { x, @@ -119,6 +118,7 @@ export default function useReactFlow(): ReactFlo const deleteElements = useCallback(({ nodes: nodesDeleted, edges: edgesDeleted }) => { const { nodeInternals, + getNodes, edges, hasDefaultNodes, hasDefaultEdges, @@ -127,10 +127,9 @@ export default function useReactFlow(): ReactFlo onNodesChange, onEdgesChange, } = store.getState(); - const nodes = Array.from(nodeInternals.values()); const nodeIds = (nodesDeleted || []).map((node) => node.id); const edgeIds = (edgesDeleted || []).map((edge) => edge.id); - const nodesToRemove = nodes.reduce((res, node) => { + const nodesToRemove = getNodes().reduce((res, node) => { const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode); const deletable = typeof node.deletable === 'boolean' ? node.deletable : true; if (deletable && (nodeIds.includes(node.id) || parentHit)) { @@ -219,7 +218,7 @@ export default function useReactFlow(): ReactFlo return []; } - return (nodes || Array.from(store.getState().nodeInternals.values())).filter((n) => { + return (nodes || store.getState().getNodes()).filter((n) => { if (!isRect && (n.id === node!.id || !n.positionAbsolute)) { return false; } diff --git a/packages/core/src/hooks/useUpdateNodePositions.ts b/packages/core/src/hooks/useUpdateNodePositions.ts index eb5880e3..56e55925 100644 --- a/packages/core/src/hooks/useUpdateNodePositions.ts +++ b/packages/core/src/hooks/useUpdateNodePositions.ts @@ -8,8 +8,8 @@ function useUpdateNodePositions() { const store = useStoreApi(); const updatePositions = useCallback((positionDiff: XYPosition) => { - const { nodeInternals, nodeExtent, updateNodePositions, snapToGrid, snapGrid } = store.getState(); - const selectedNodes = Array.from(nodeInternals.values()).filter((n) => n.selected); + const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid } = store.getState(); + const selectedNodes = getNodes().filter((n) => n.selected); const nodeUpdates = selectedNodes.map((n) => { if (n.positionAbsolute) { diff --git a/packages/core/src/hooks/useVisibleNodes.ts b/packages/core/src/hooks/useVisibleNodes.ts index f74be159..b076b126 100644 --- a/packages/core/src/hooks/useVisibleNodes.ts +++ b/packages/core/src/hooks/useVisibleNodes.ts @@ -10,7 +10,7 @@ function useVisibleNodes(onlyRenderVisible: boolean) { (s: ReactFlowState) => onlyRenderVisible ? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true) - : Array.from(s.nodeInternals.values()), + : s.getNodes(), [onlyRenderVisible] ) ); diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index bdb02137..b593faff 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -27,6 +27,9 @@ const createRFStore = () => const { nodeInternals, nodeOrigin } = get(); set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin) }); }, + getNodes: () => { + return Array.from(get().nodeInternals.values()); + }, setEdges: (edges: Edge[]) => { const { defaultEdgeOptions = {} } = get(); set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) }); @@ -125,11 +128,11 @@ const createRFStore = () => }, triggerNodeChanges: (changes: NodeChange[]) => { - const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin } = get(); + const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin, getNodes } = get(); if (changes?.length) { if (hasDefaultNodes) { - const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values())); + const nodes = applyNodeChanges(changes, getNodes()); const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin); set({ nodeInternals: nextNodeInternals }); } @@ -139,14 +142,14 @@ const createRFStore = () => }, addSelectedNodes: (selectedNodeIds: string[]) => { - const { multiSelectionActive, nodeInternals, edges } = get(); + const { multiSelectionActive, edges, getNodes } = get(); let changedNodes: NodeSelectionChange[]; let changedEdges: EdgeSelectionChange[] | null = null; if (multiSelectionActive) { changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true)) as NodeSelectionChange[]; } else { - changedNodes = getSelectionChanges(Array.from(nodeInternals.values()), selectedNodeIds); + changedNodes = getSelectionChanges(getNodes(), selectedNodeIds); changedEdges = getSelectionChanges(edges, []); } @@ -158,7 +161,7 @@ const createRFStore = () => }); }, addSelectedEdges: (selectedEdgeIds: string[]) => { - const { multiSelectionActive, edges, nodeInternals } = get(); + const { multiSelectionActive, edges, getNodes } = get(); let changedEdges: EdgeSelectionChange[]; let changedNodes: NodeSelectionChange[] | null = null; @@ -166,7 +169,7 @@ const createRFStore = () => changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true)) as EdgeSelectionChange[]; } else { changedEdges = getSelectionChanges(edges, selectedEdgeIds); - changedNodes = getSelectionChanges(Array.from(nodeInternals.values()), []); + changedNodes = getSelectionChanges(getNodes(), []); } updateNodesAndEdgesSelections({ @@ -177,8 +180,8 @@ const createRFStore = () => }); }, unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => { - const { nodeInternals, edges: storeEdges } = get(); - const nodesToUnselect = nodes ? nodes : Array.from(nodeInternals.values()); + const { edges: storeEdges, getNodes } = get(); + const nodesToUnselect = nodes ? nodes : getNodes(); const edgesToUnselect = edges ? edges : storeEdges; const changedNodes = nodesToUnselect.map((n) => { @@ -215,8 +218,8 @@ const createRFStore = () => set({ translateExtent }); }, resetSelectedElements: () => { - const { nodeInternals, edges } = get(); - const nodes = Array.from(nodeInternals.values()); + const { edges, getNodes } = get(); + const nodes = getNodes(); const nodesToUnselect = nodes .filter((e) => e.selected) diff --git a/packages/core/src/store/utils.ts b/packages/core/src/store/utils.ts index 02a4516b..d0d976fc 100644 --- a/packages/core/src/store/utils.ts +++ b/packages/core/src/store/utils.ts @@ -120,7 +120,7 @@ type InternalFitViewOptions = { export function fitView(get: StoreApi['getState'], options: InternalFitViewOptions = {}) { const { - nodeInternals, + getNodes, width, height, minZoom, @@ -134,9 +134,7 @@ export function fitView(get: StoreApi['getState'], options: Inte if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) { if (d3Zoom && d3Selection) { - const nodes = Array.from(nodeInternals.values()).filter((n) => - options.includeHiddenNodes ? n.width && n.height : !n.hidden - ); + const nodes = getNodes().filter((n) => (options.includeHiddenNodes ? n.width && n.height : !n.hidden)); const nodesInitialized = nodes.every((n) => n.width && n.height); diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index 472df101..0d40c38c 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -212,6 +212,7 @@ export type ReactFlowStore = { export type ReactFlowActions = { setNodes: (nodes: Node[]) => void; + getNodes: () => Node[]; setEdges: (edges: Edge[]) => void; setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void; updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void; diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index b1d4c555..ecce55aa 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -26,7 +26,7 @@ const defaultWidth = 200; const defaultHeight = 150; const selector = (s: ReactFlowState) => { - const nodes = Array.from(s.nodeInternals.values()); + const nodes = s.getNodes(); const viewBB: Rect = { x: -s.transform[0] / s.transform[2], y: -s.transform[1] / s.transform[2], diff --git a/packages/node-toolbar/src/NodeToolbar.tsx b/packages/node-toolbar/src/NodeToolbar.tsx index 5c6cbc86..aed9d860 100644 --- a/packages/node-toolbar/src/NodeToolbar.tsx +++ b/packages/node-toolbar/src/NodeToolbar.tsx @@ -31,7 +31,7 @@ const nodesEqualityFn = (a: Node[], b: Node[]) => { const storeSelector = (state: ReactFlowState) => ({ transform: state.transform, nodeOrigin: state.nodeOrigin, - selectedNodesCount: Array.from(state.nodeInternals.values()).filter((node) => node.selected).length, + selectedNodesCount: state.getNodes().filter((node) => node.selected).length, }); function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number): string {