diff --git a/example/src/UseZoomPanHelper/index.tsx b/example/src/UseReactFlow/index.tsx similarity index 80% rename from example/src/UseZoomPanHelper/index.tsx rename to example/src/UseReactFlow/index.tsx index 55aeae65..d980d3a7 100644 --- a/example/src/UseZoomPanHelper/index.tsx +++ b/example/src/UseReactFlow/index.tsx @@ -33,7 +33,7 @@ const UseZoomPanHelperFlow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); - const { project, setCenter, zoomIn, zoomOut, fitView } = useReactFlow(); + const { project, setCenter, zoomIn, zoomOut, fitView, addNodes, setNodes: setNodesHook } = useReactFlow(); const onPaneClick = useCallback( (evt) => { @@ -49,7 +49,7 @@ const UseZoomPanHelperFlow = () => { }) ); }, - [project] + [project, setNodes] ); const onNodeClick = useCallback( @@ -60,6 +60,22 @@ const UseZoomPanHelperFlow = () => { [setCenter] ); + const onAddNode = useCallback(() => { + const newNode = { + id: getId(), + position: { x: Math.random() * 500, y: Math.random() * 500 }, + data: { + label: 'New Node', + }, + }; + + addNodes(newNode); + }, [addNodes]); + + const onResetNodes = useCallback(() => { + setNodesHook(initialNodes); + }, [setNodesHook]); + return ( { + + diff --git a/example/src/index.tsx b/example/src/index.tsx index 1208c967..5f848abd 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -31,11 +31,9 @@ import Undirectional from './Undirectional'; import UpdatableEdge from './UpdatableEdge'; import UpdateNode from './UpdateNode'; import UseUpdateNodeInternals from './UseUpdateNodeInternals'; -import UseZoomPanHelper from './UseZoomPanHelper'; +import UseReactFlow from './UseReactFlow'; import Validation from './Validation'; - - const routes = [ { path: '/', @@ -146,8 +144,8 @@ const routes = [ component: UpdateNode, }, { - path: '/usezoompanhelper', - component: UseZoomPanHelper, + path: '/usereactflow', + component: UseReactFlow, }, { path: '/useupdatenodeinternals', @@ -169,18 +167,18 @@ const Header = () => { const onChange = (event: ChangeEvent) => navigate(event.target.value); return ( -
- - React Flow Dev - - -
+
+ + React Flow Dev + + +
); }; @@ -189,7 +187,7 @@ ReactDOM.render(
{routes.map((route) => ( - }/> + } /> ))} , diff --git a/src/hooks/useReactFlow.ts b/src/hooks/useReactFlow.ts index 3be4d9a7..bbe310e8 100644 --- a/src/hooks/useReactFlow.ts +++ b/src/hooks/useReactFlow.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import useViewportHelper from './useViewportHelper'; import { useStoreApi } from '../store'; -import { ReactFlowInstance, Instance } from '../types'; +import { ReactFlowInstance, Instance, NodeAddChange, EdgeAddChange, NodeResetChange, EdgeResetChange } from '../types'; export default function useReactFlow(): ReactFlowInstance { const { initialized: viewportInitialized, ...viewportHelperFunctions } = useViewportHelper(); @@ -30,30 +30,54 @@ export default function useReactFlow(): ReactFlo }, []); const setNodes = useCallback>((payload) => { - const { nodeInternals, setNodes } = store.getState(); + const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); const nodes = Array.from(nodeInternals.values()); const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; - setNodes(nextNodes); + + if (hasDefaultNodes) { + setNodes(nextNodes); + } else if (onNodesChange) { + const changes = nextNodes.map((node) => ({ item: node, type: 'reset' } as NodeResetChange)); + onNodesChange(changes); + } }, []); const setEdges = useCallback>((payload) => { - const { edges = [], setEdges } = store.getState(); + const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState(); const nextEdges = typeof payload === 'function' ? payload(edges) : payload; - setEdges(nextEdges); + + if (hasDefaultEdges) { + setEdges(nextEdges); + } else if (onEdgesChange) { + const changes = nextEdges.map((edge) => ({ item: edge, type: 'reset' } as EdgeResetChange)); + onEdgesChange(changes); + } }, []); const addNodes = useCallback>((payload) => { const nodes = Array.isArray(payload) ? payload : [payload]; - const { nodeInternals, setNodes } = store.getState(); - const currentNodes = Array.from(nodeInternals.values()); - const nextNodes = [...currentNodes, ...nodes]; - setNodes(nextNodes); + const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); + + if (hasDefaultNodes) { + const currentNodes = Array.from(nodeInternals.values()); + const nextNodes = [...currentNodes, ...nodes]; + setNodes(nextNodes); + } else if (onNodesChange) { + const changes = nodes.map((node) => ({ item: node, type: 'add' } as NodeAddChange)); + onNodesChange(changes); + } }, []); const addEdges = useCallback>((payload) => { const nextEdges = Array.isArray(payload) ? payload : [payload]; - const { edges = [], setEdges } = store.getState(); - setEdges([...edges, ...nextEdges]); + const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState(); + + if (hasDefaultEdges) { + setEdges([...edges, ...nextEdges]); + } else if (onEdgesChange) { + const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' } as EdgeAddChange)); + onEdgesChange(changes); + } }, []); const toObject = useCallback>(() => { diff --git a/src/types/changes.ts b/src/types/changes.ts index a071f463..d896d03e 100644 --- a/src/types/changes.ts +++ b/src/types/changes.ts @@ -1,5 +1,6 @@ import { XYPosition, Dimensions } from './utils'; -import { NodeHandleBounds } from './nodes'; +import { NodeHandleBounds, Node } from './nodes'; +import { Edge } from './edges'; export type NodeDimensionChange = { id: string; @@ -26,8 +27,32 @@ export type NodeRemoveChange = { type: 'remove'; }; -export type NodeChange = NodeDimensionChange | NodePositionChange | NodeSelectionChange | NodeRemoveChange; +export type NodeAddChange = { + item: Node; + type: 'add'; +}; + +export type NodeResetChange = { + item: Node; + type: 'reset'; +}; + +export type NodeChange = + | NodeDimensionChange + | NodePositionChange + | NodeSelectionChange + | NodeRemoveChange + | NodeAddChange + | NodeResetChange; export type EdgeSelectionChange = NodeSelectionChange; export type EdgeRemoveChange = NodeRemoveChange; -export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange; +export type EdgeAddChange = { + item: Edge; + type: 'add'; +}; +export type EdgeResetChange = { + item: Edge; + type: 'reset'; +}; +export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeResetChange; diff --git a/src/utils/changes.ts b/src/utils/changes.ts index e959a766..e5278a6f 100644 --- a/src/utils/changes.ts +++ b/src/utils/changes.ts @@ -44,8 +44,14 @@ function handleParentExpand(res: any[], updateItem: any) { } } -function applyChanges(changes: NodeChange[] | EdgeChange[], elements: any[]): any[] { - const initElements: any[] = []; +function applyChanges(changes: any[], elements: any[]): any[] { + // unfortunately we need this hack to handle the setNodes and setEdges function of the + // useReactFlow hook. + if (changes.some((c) => c.type === 'reset')) { + return changes.filter((c) => c.type === 'reset').map((c) => c.item); + } + + const initElements: any[] = changes.filter((c) => c.type === 'add').map((c) => c.item); return elements.reduce((res: any[], item: any) => { const currentChange = changes.find((c) => c.id === item.id);