fix(useReactFlow): use onNodesChange and onEdgesChange

This commit is contained in:
moklick
2022-03-15 18:38:58 +01:00
parent 514c15c0ef
commit 5b32746422
5 changed files with 107 additions and 36 deletions
@@ -33,7 +33,7 @@ const UseZoomPanHelperFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); 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( const onPaneClick = useCallback(
(evt) => { (evt) => {
@@ -49,7 +49,7 @@ const UseZoomPanHelperFlow = () => {
}) })
); );
}, },
[project] [project, setNodes]
); );
const onNodeClick = useCallback( const onNodeClick = useCallback(
@@ -60,6 +60,22 @@ const UseZoomPanHelperFlow = () => {
[setCenter] [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 ( return (
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
@@ -77,6 +93,8 @@ const UseZoomPanHelperFlow = () => {
<button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button> <button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button>
<button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button> <button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button>
<button onClick={() => fitView({ duration: 1200, padding: 0.3 })}>fitView</button> <button onClick={() => fitView({ duration: 1200, padding: 0.3 })}>fitView</button>
<button onClick={onAddNode}>add node</button>
<button onClick={onResetNodes}>reset nodes</button>
</div> </div>
<Background /> <Background />
<MiniMap /> <MiniMap />
+16 -18
View File
@@ -31,11 +31,9 @@ import Undirectional from './Undirectional';
import UpdatableEdge from './UpdatableEdge'; import UpdatableEdge from './UpdatableEdge';
import UpdateNode from './UpdateNode'; import UpdateNode from './UpdateNode';
import UseUpdateNodeInternals from './UseUpdateNodeInternals'; import UseUpdateNodeInternals from './UseUpdateNodeInternals';
import UseZoomPanHelper from './UseZoomPanHelper'; import UseReactFlow from './UseReactFlow';
import Validation from './Validation'; import Validation from './Validation';
const routes = [ const routes = [
{ {
path: '/', path: '/',
@@ -146,8 +144,8 @@ const routes = [
component: UpdateNode, component: UpdateNode,
}, },
{ {
path: '/usezoompanhelper', path: '/usereactflow',
component: UseZoomPanHelper, component: UseReactFlow,
}, },
{ {
path: '/useupdatenodeinternals', path: '/useupdatenodeinternals',
@@ -169,18 +167,18 @@ const Header = () => {
const onChange = (event: ChangeEvent<HTMLSelectElement>) => navigate(event.target.value); const onChange = (event: ChangeEvent<HTMLSelectElement>) => navigate(event.target.value);
return ( return (
<header> <header>
<a className="logo" href="https://github.com/wbkd/react-flow"> <a className="logo" href="https://github.com/wbkd/react-flow">
React Flow Dev React Flow Dev
</a> </a>
<select defaultValue={location.pathname} onChange={onChange}> <select defaultValue={location.pathname} onChange={onChange}>
{routes.map((route) => ( {routes.map((route) => (
<option value={route.path} key={route.path}> <option value={route.path} key={route.path}>
{route.path === '/' ? 'overview' : route.path.substring(1, route.path.length)} {route.path === '/' ? 'overview' : route.path.substring(1, route.path.length)}
</option> </option>
))} ))}
</select> </select>
</header> </header>
); );
}; };
@@ -189,7 +187,7 @@ ReactDOM.render(
<Header /> <Header />
<Routes> <Routes>
{routes.map((route) => ( {routes.map((route) => (
<Route path={route.path} key={route.path} element={<route.component />}/> <Route path={route.path} key={route.path} element={<route.component />} />
))} ))}
</Routes> </Routes>
</BrowserRouter>, </BrowserRouter>,
+35 -11
View File
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
import useViewportHelper from './useViewportHelper'; import useViewportHelper from './useViewportHelper';
import { useStoreApi } from '../store'; import { useStoreApi } from '../store';
import { ReactFlowInstance, Instance } from '../types'; import { ReactFlowInstance, Instance, NodeAddChange, EdgeAddChange, NodeResetChange, EdgeResetChange } from '../types';
export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> { export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> {
const { initialized: viewportInitialized, ...viewportHelperFunctions } = useViewportHelper(); const { initialized: viewportInitialized, ...viewportHelperFunctions } = useViewportHelper();
@@ -30,30 +30,54 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
}, []); }, []);
const setNodes = useCallback<Instance.SetNodes<NodeData>>((payload) => { const setNodes = useCallback<Instance.SetNodes<NodeData>>((payload) => {
const { nodeInternals, setNodes } = store.getState(); const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
const nodes = Array.from(nodeInternals.values()); const nodes = Array.from(nodeInternals.values());
const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; 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<NodeData>));
onNodesChange(changes);
}
}, []); }, []);
const setEdges = useCallback<Instance.SetEdges<EdgeData>>((payload) => { const setEdges = useCallback<Instance.SetEdges<EdgeData>>((payload) => {
const { edges = [], setEdges } = store.getState(); const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
const nextEdges = typeof payload === 'function' ? payload(edges) : payload; 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<EdgeData>));
onEdgesChange(changes);
}
}, []); }, []);
const addNodes = useCallback<Instance.AddNodes<NodeData>>((payload) => { const addNodes = useCallback<Instance.AddNodes<NodeData>>((payload) => {
const nodes = Array.isArray(payload) ? payload : [payload]; const nodes = Array.isArray(payload) ? payload : [payload];
const { nodeInternals, setNodes } = store.getState(); const { nodeInternals, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
const currentNodes = Array.from(nodeInternals.values());
const nextNodes = [...currentNodes, ...nodes]; if (hasDefaultNodes) {
setNodes(nextNodes); 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<NodeData>));
onNodesChange(changes);
}
}, []); }, []);
const addEdges = useCallback<Instance.AddEdges<EdgeData>>((payload) => { const addEdges = useCallback<Instance.AddEdges<EdgeData>>((payload) => {
const nextEdges = Array.isArray(payload) ? payload : [payload]; const nextEdges = Array.isArray(payload) ? payload : [payload];
const { edges = [], setEdges } = store.getState(); const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
setEdges([...edges, ...nextEdges]);
if (hasDefaultEdges) {
setEdges([...edges, ...nextEdges]);
} else if (onEdgesChange) {
const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' } as EdgeAddChange<EdgeData>));
onEdgesChange(changes);
}
}, []); }, []);
const toObject = useCallback<Instance.ToObject<NodeData, EdgeData>>(() => { const toObject = useCallback<Instance.ToObject<NodeData, EdgeData>>(() => {
+28 -3
View File
@@ -1,5 +1,6 @@
import { XYPosition, Dimensions } from './utils'; import { XYPosition, Dimensions } from './utils';
import { NodeHandleBounds } from './nodes'; import { NodeHandleBounds, Node } from './nodes';
import { Edge } from './edges';
export type NodeDimensionChange = { export type NodeDimensionChange = {
id: string; id: string;
@@ -26,8 +27,32 @@ export type NodeRemoveChange = {
type: 'remove'; type: 'remove';
}; };
export type NodeChange = NodeDimensionChange | NodePositionChange | NodeSelectionChange | NodeRemoveChange; export type NodeAddChange<NodeData = any> = {
item: Node<NodeData>;
type: 'add';
};
export type NodeResetChange<NodeData = any> = {
item: Node<NodeData>;
type: 'reset';
};
export type NodeChange =
| NodeDimensionChange
| NodePositionChange
| NodeSelectionChange
| NodeRemoveChange
| NodeAddChange
| NodeResetChange;
export type EdgeSelectionChange = NodeSelectionChange; export type EdgeSelectionChange = NodeSelectionChange;
export type EdgeRemoveChange = NodeRemoveChange; export type EdgeRemoveChange = NodeRemoveChange;
export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange; export type EdgeAddChange<EdgeData = any> = {
item: Edge<EdgeData>;
type: 'add';
};
export type EdgeResetChange<EdgeData = any> = {
item: Edge<EdgeData>;
type: 'reset';
};
export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeResetChange;
+8 -2
View File
@@ -44,8 +44,14 @@ function handleParentExpand(res: any[], updateItem: any) {
} }
} }
function applyChanges(changes: NodeChange[] | EdgeChange[], elements: any[]): any[] { function applyChanges(changes: any[], elements: any[]): any[] {
const initElements: 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) => { return elements.reduce((res: any[], item: any) => {
const currentChange = changes.find((c) => c.id === item.id); const currentChange = changes.find((c) => c.id === item.id);