diff --git a/examples/react/src/examples/CustomMiniMapNode/index.tsx b/examples/react/src/examples/CustomMiniMapNode/index.tsx
index 0b62196f..7ecf1c54 100644
--- a/examples/react/src/examples/CustomMiniMapNode/index.tsx
+++ b/examples/react/src/examples/CustomMiniMapNode/index.tsx
@@ -27,9 +27,9 @@ const buttonStyle: CSSProperties = {
zIndex: 4,
};
-const CustomMiniMapNode = ({ x, y, width, height, color }: MiniMapNodeProps) => (
-
-);
+const CustomMiniMapNode = ({ x, y, width, height }: MiniMapNodeProps) => {
+ return ;
+};
const CustomMiniMapNodeFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
diff --git a/examples/react/src/examples/Subflow/index.tsx b/examples/react/src/examples/Subflow/index.tsx
index 19dc119a..8ea28e34 100644
--- a/examples/react/src/examples/Subflow/index.tsx
+++ b/examples/react/src/examples/Subflow/index.tsx
@@ -90,7 +90,7 @@ const initialNodes: Node[] = [
data: { label: 'Node 5' },
position: { x: 650, y: 250 },
className: 'light',
- style: { width: 400, height: 150 },
+ style: { width: 100, height: 100 },
zIndex: 1000,
},
{
@@ -161,9 +161,12 @@ const Subflow = () => {
setNodes((nds) => {
return nds.map((n) => {
if (!n.parentNode) {
- n.position = {
- x: Math.random() * 400,
- y: Math.random() * 400,
+ return {
+ ...n,
+ position: {
+ x: Math.random() * 400,
+ y: Math.random() * 400,
+ },
};
}
@@ -178,8 +181,10 @@ const Subflow = () => {
const toggleClassnames = () => {
setNodes((nds) => {
return nds.map((n) => {
- n.className = n.className === 'light' ? 'dark' : 'light';
- return n;
+ return {
+ ...n,
+ className: n.className === 'light' ? 'dark' : 'light',
+ };
});
});
};
@@ -187,8 +192,10 @@ const Subflow = () => {
const toggleChildNodes = () => {
setNodes((nds) => {
return nds.map((n) => {
- n.hidden = !!n.parentNode && !n.hidden;
- return n;
+ return {
+ ...n,
+ hidden: !!n.parentNode && !n.hidden,
+ };
});
});
};
@@ -215,19 +222,12 @@ const Subflow = () => {
-
-
-
-
+
+
+
+
+
);
diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx
index 28323a19..5973751d 100644
--- a/packages/react/src/components/EdgeWrapper/index.tsx
+++ b/packages/react/src/components/EdgeWrapper/index.tsx
@@ -101,12 +101,12 @@ export function EdgeWrapper({
);
const markerStartUrl = useMemo(
- () => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined),
+ () => (edge.markerStart ? `url('#${getMarkerId(edge.markerStart, rfId)}')` : undefined),
[edge.markerStart, rfId]
);
const markerEndUrl = useMemo(
- () => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined),
+ () => (edge.markerEnd ? `url('#${getMarkerId(edge.markerEnd, rfId)}')` : undefined),
[edge.markerEnd, rfId]
);
diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx
index ebf65163..548a4aa4 100644
--- a/packages/react/src/components/NodeWrapper/index.tsx
+++ b/packages/react/src/components/NodeWrapper/index.tsx
@@ -79,16 +79,33 @@ export function NodeWrapper({
const prevTargetPosition = useRef(node.targetPosition);
const prevType = useRef(nodeType);
+ const width = node.width ?? undefined;
+ const height = node.height ?? undefined;
+ const computedWidth = node.computed?.width;
+ const computedHeight = node.computed?.height;
+ const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
+ const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
+
const moveSelectedNodes = useMoveSelectedNodes();
+ useEffect(() => {
+ return () => {
+ if (nodeRef.current) {
+ resizeObserver?.unobserve(nodeRef.current);
+ }
+ };
+ }, []);
+
useEffect(() => {
if (nodeRef.current && !node.hidden) {
const currNode = nodeRef.current;
- resizeObserver?.observe(currNode);
- return () => resizeObserver?.unobserve(currNode);
+ if (!initialized || !hasHandleBounds) {
+ resizeObserver?.unobserve(currNode);
+ resizeObserver?.observe(currNode);
+ }
}
- }, [node.hidden]);
+ }, [node.hidden, initialized, hasHandleBounds]);
useEffect(() => {
// when the user programmatically changes the source or handle position, we re-initialize the node
@@ -123,11 +140,6 @@ export function NodeWrapper({
return null;
}
- const width = node.width ?? undefined;
- const height = node.height ?? undefined;
- const computedWidth = node.computed?.width;
- const computedHeight = node.computed?.height;
-
const positionAbsoluteOrigin = getPositionWithOrigin({
x: positionAbsoluteX,
y: positionAbsoluteY,
@@ -135,7 +147,6 @@ export function NodeWrapper({
height: computedHeight ?? height ?? 0,
origin: node.origin || nodeOrigin,
});
- const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
const onMouseEnterHandler = onMouseEnter ? (event: MouseEvent) => onMouseEnter(event, { ...node }) : undefined;
diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx
index 96080368..f1d68e6c 100644
--- a/packages/react/src/components/StoreUpdater/index.tsx
+++ b/packages/react/src/components/StoreUpdater/index.tsx
@@ -9,7 +9,7 @@ import { infiniteExtent, type CoordinateExtent } from '@xyflow/system';
import { useStore, useStoreApi } from '../../hooks/useStore';
import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types';
-import { initNodeOrigin } from '../../container/ReactFlow';
+import { defaultNodeOrigin } from '../../container/ReactFlow/init-values';
// these fields exist in the global store and we need to keep them up to date
const reactFlowFieldsToTrack = [
@@ -84,6 +84,19 @@ const selector = (s: ReactFlowState) => ({
setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
});
+const initPrevValues = {
+ // these are values that are also passed directly to other components
+ // than the StoreUpdater. We can reduce the number of setStore calls
+ // by setting the same values here as prev fields.
+ translateExtent: infiniteExtent,
+ nodeOrigin: defaultNodeOrigin,
+ minZoom: 0.5,
+ maxZoom: 2,
+ elementsSelectable: true,
+ noPanClassName: 'nopan',
+ rfId: '1',
+};
+
export function StoreUpdater(props: StoreUpdaterProps) {
const {
setNodes,
@@ -99,23 +112,15 @@ export function StoreUpdater(props: StoreUpdaterProps) {
useEffect(() => {
setDefaultNodesAndEdges(props.defaultNodes, props.defaultEdges);
+
return () => {
+ // when we reset the store we also need to reset the previous fields
+ previousFields.current = initPrevValues;
reset();
};
}, []);
- const previousFields = useRef>({
- // these are values that are also passed directly to other components
- // than the StoreUpdater. We can reduce the number of setStore calls
- // by setting the same values here as prev fields.
- translateExtent: infiniteExtent,
- nodeOrigin: initNodeOrigin,
- minZoom: 0.5,
- maxZoom: 2,
- elementsSelectable: true,
- noPanClassName: 'nopan',
- rfId: '1',
- });
+ const previousFields = useRef>(initPrevValues);
useEffect(
() => {
diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx
index df9936a1..6448abe4 100644
--- a/packages/react/src/container/ReactFlow/index.tsx
+++ b/packages/react/src/container/ReactFlow/index.tsx
@@ -1,14 +1,6 @@
import { forwardRef, type CSSProperties } from 'react';
import cc from 'classcat';
-import {
- ConnectionLineType,
- PanOnScrollMode,
- SelectionMode,
- infiniteExtent,
- isMacOs,
- type NodeOrigin,
- type Viewport,
-} from '@xyflow/system';
+import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system';
import { A11yDescriptions } from '../../components/A11yDescriptions';
import { Attribution } from '../../components/Attribution';
@@ -18,9 +10,7 @@ import { useColorModeClass } from '../../hooks/useColorModeClass';
import { GraphView } from '../GraphView';
import { Wrapper } from './Wrapper';
import type { ReactFlowProps, ReactFlowRefType } from '../../types';
-
-export const initNodeOrigin: NodeOrigin = [0, 0];
-const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
+import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values';
const wrapperStyle: CSSProperties = {
width: '100%',
@@ -89,11 +79,11 @@ const ReactFlow = forwardRef(
nodesDraggable,
nodesConnectable,
nodesFocusable,
- nodeOrigin = initNodeOrigin,
+ nodeOrigin = defaultNodeOrigin,
edgesFocusable,
edgesUpdatable,
elementsSelectable = true,
- defaultViewport = initDefaultViewport,
+ defaultViewport = initViewport,
minZoom = 0.5,
maxZoom = 2,
translateExtent = infiniteExtent,
diff --git a/packages/react/src/container/ReactFlow/init-values.ts b/packages/react/src/container/ReactFlow/init-values.ts
new file mode 100644
index 00000000..75e7fcaa
--- /dev/null
+++ b/packages/react/src/container/ReactFlow/init-values.ts
@@ -0,0 +1,4 @@
+import { type NodeOrigin, Viewport } from '@xyflow/system';
+
+export const defaultNodeOrigin: NodeOrigin = [0, 0];
+export const defaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts
index 46447e00..71a51a1e 100644
--- a/packages/react/src/store/index.ts
+++ b/packages/react/src/store/index.ts
@@ -303,13 +303,7 @@ const createRFStore = ({
set(currentConnection);
},
- reset: () => {
- // @todo: what should we do about this? Do we still need it?
- // if you are on a SPA with multiple flows, we want to make sure that the store gets resetted
- // when you switch pages. Does this reset solves this? Currently it always gets called. This
- // leads to an emtpy nodes array at the beginning.
- // set({ ...getInitialState() });
- },
+ reset: () => set({ ...getInitialState() }),
}),
Object.is
);
diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts
index 5cb8a382..c04c7e51 100644
--- a/packages/system/src/utils/store.ts
+++ b/packages/system/src/utils/store.ts
@@ -94,8 +94,8 @@ export function adoptUserProvidedNodes(
...n,
computed: {
positionAbsolute: n.position,
- width: n.computed?.width || currentStoreNode?.computed?.width,
- height: n.computed?.height || currentStoreNode?.computed?.height,
+ width: n.computed?.width,
+ height: n.computed?.height,
},
};
const z = (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0);