diff --git a/example/src/UseUpdateNodeInternals/CustomNode.tsx b/example/src/UseUpdateNodeInternals/CustomNode.tsx new file mode 100644 index 00000000..ecae9f4f --- /dev/null +++ b/example/src/UseUpdateNodeInternals/CustomNode.tsx @@ -0,0 +1,26 @@ +import React, { memo, FC, useMemo, CSSProperties } from 'react'; + +import { Handle, Position, NodeProps } from 'react-flow-renderer'; + +const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' }; + +const CustomNode: FC = ({ data }) => { + const handles = useMemo( + () => + Array.from({ length: data.handleCount }, (x, i) => { + const handleId = `handle-${i}`; + return ; + }), + [data.handleCount] + ); + + return ( +
+ +
output handle count: {data.handleCount}
+ {handles} +
+ ); +}; + +export default memo(CustomNode); diff --git a/example/src/UseUpdateNodeInternals/index.tsx b/example/src/UseUpdateNodeInternals/index.tsx new file mode 100644 index 00000000..135e5cb2 --- /dev/null +++ b/example/src/UseUpdateNodeInternals/index.tsx @@ -0,0 +1,81 @@ +import React, { useState, useCallback, CSSProperties, useRef } from 'react'; + +import ReactFlow, { + NodeTypesType, + addEdge, + useZoomPanHelper, + ReactFlowProvider, + Elements, + Connection, + Edge, + ElementId, + useUpdateNodeInternals, + Position, +} from 'react-flow-renderer'; +import CustomNode from './CustomNode'; + +const initialHandleCount = 1; + +const initialElements: Elements = [ + { + id: '1', + type: 'custom', + data: { label: 'Node 1', handleCount: initialHandleCount }, + position: { x: 250, y: 5 }, + className: 'light', + }, +]; + +const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 10 }; + +const nodeTypes: NodeTypesType = { + custom: CustomNode, +}; + +let id = 5; +const getId = (): ElementId => `${id++}`; + +const UpdateNodeInternalsFlow = () => { + const handleCount = useRef(initialHandleCount); + const [elements, setElements] = useState(initialElements); + const updateNodeInternals = useUpdateNodeInternals(); + const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const { project } = useZoomPanHelper(); + + const onPaneClick = useCallback( + (evt) => + setElements((els) => + els.concat({ + id: getId(), + position: project({ x: evt.clientX, y: evt.clientY - 40 }), + data: { label: 'new node' }, + targetPosition: Position.Left, + sourcePosition: Position.Right, + }) + ), + [project] + ); + const toggleHandleCount = useCallback(() => { + handleCount.current = handleCount.current === 1 ? 2 : 1; + setElements((els) => els.map((el) => ({ ...el, data: { ...el.data, handleCount: handleCount.current } }))); + }, []); + + const updateNode = useCallback(() => updateNodeInternals('1'), [updateNodeInternals]); + + return ( + +
+ + +
+
+ ); +}; + +const WrappedFlow = () => ( + + + +); + +export default WrappedFlow; diff --git a/example/src/index.tsx b/example/src/index.tsx index eb249c21..9c6b930b 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -22,6 +22,7 @@ import DragNDrop from './DragNDrop'; import Layout from './Layouting'; import SwitchFlows from './Switch'; import UseZoomPanHelper from './UseZoomPanHelper'; +import UseUpdateNodeInternals from './UseUpdateNodeInternals'; import './index.css'; @@ -106,6 +107,10 @@ const routes = [ path: '/usezoompanhelper', component: UseZoomPanHelper, }, + { + path: '/useupdatenodeinternals', + component: UseUpdateNodeInternals, + }, ]; const Header = withRouter(({ history, location }) => { diff --git a/src/hooks/useUpdateNodeInternals.ts b/src/hooks/useUpdateNodeInternals.ts new file mode 100644 index 00000000..7d64af8a --- /dev/null +++ b/src/hooks/useUpdateNodeInternals.ts @@ -0,0 +1,18 @@ +import { useCallback } from 'react'; + +import { useStoreActions } from '../store/hooks'; +import { ElementId, UpdateNodeInternals } from '../types'; + +function useUpdateNodeInternals(): UpdateNodeInternals { + const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions); + + return useCallback((id: ElementId) => { + const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`); + + if (nodeElement) { + updateNodeDimensions([{ id, nodeElement, forceUpdate: true }]); + } + }, []); +} + +export default useUpdateNodeInternals; diff --git a/src/index.ts b/src/index.ts index 52ecf562..1d811790 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ export { getTransformForBounds, } from './utils/graph'; export { default as useZoomPanHelper } from './hooks/useZoomPanHelper'; +export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals'; export * from './additional-components'; export * from './store/hooks'; diff --git a/src/types/index.ts b/src/types/index.ts index 7b10a466..7ec79fe1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -425,3 +425,5 @@ export interface ReactFlowState { onConnectStop?: OnConnectStopFunc; onConnectEnd?: OnConnectEndFunc; } + +export type UpdateNodeInternals = (nodeId: ElementId) => void;