From 494eb09a21933fd490c138250b238a61a405e4be Mon Sep 17 00:00:00 2001 From: moklick Date: Sun, 17 Dec 2023 09:37:47 +0100 Subject: [PATCH 1/2] chore(examples): cleanup astro --- .../src/components/ReactFlowExample/CustomNode.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/astro-xyflow/src/components/ReactFlowExample/CustomNode.tsx b/examples/astro-xyflow/src/components/ReactFlowExample/CustomNode.tsx index 86aef137..6dc30709 100644 --- a/examples/astro-xyflow/src/components/ReactFlowExample/CustomNode.tsx +++ b/examples/astro-xyflow/src/components/ReactFlowExample/CustomNode.tsx @@ -7,7 +7,7 @@ const sourceHandleStyleB: CSSProperties = { left: 'auto', }; -const CustomNode: FC = ({ data, positionAbsolute }) => { +const CustomNode: FC = ({ data, positionAbsoluteX, positionAbsoluteY }) => { return ( <> @@ -18,7 +18,7 @@ const CustomNode: FC = ({ data, positionAbsolute }) => {
Position:{' '} - {positionAbsolute.x.toFixed(2)},{positionAbsolute.y.toFixed(2)} + {positionAbsoluteX.toFixed(2)},{positionAbsoluteY.toFixed(2)}
From 82709a4bc1d559743da0cb71f1ba451d8e52119f Mon Sep 17 00:00:00 2001 From: moklick Date: Sun, 17 Dec 2023 10:46:07 +0100 Subject: [PATCH 2/2] refactor(storeUpdater): only use one useEffect,cleanup based on #3677 --- .../src/components/StoreUpdater/index.tsx | 276 ++++++------------ 1 file changed, 94 insertions(+), 182 deletions(-) diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index 65f1bb3c..f49dec43 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -3,67 +3,72 @@ * We distinguish between values we can update directly with `useDirectStoreUpdater` (like `snapGrid`) * and values that have a dedicated setter function in the store (like `setNodes`). */ -import { useEffect } from 'react'; -import { StoreApi } from 'zustand'; +import { useEffect, useRef } from 'react'; import { shallow } from 'zustand/shallow'; -import { devWarn, type CoordinateExtent } from '@xyflow/system'; +import { type CoordinateExtent } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; -import type { Node, Edge, ReactFlowState, ReactFlowProps, ReactFlowStore } from '../../types'; +import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types'; -type StoreUpdaterProps = Pick< - ReactFlowProps, - | 'nodes' - | 'edges' - | 'defaultNodes' - | 'defaultEdges' - | 'onConnect' - | 'onConnectStart' - | 'onConnectEnd' - | 'onClickConnectStart' - | 'onClickConnectEnd' - | 'nodesDraggable' - | 'nodesConnectable' - | 'nodesFocusable' - | 'edgesFocusable' - | 'edgesUpdatable' - | 'minZoom' - | 'maxZoom' - | 'nodeExtent' - | 'onNodesChange' - | 'onEdgesChange' - | 'elementsSelectable' - | 'connectionMode' - | 'snapToGrid' - | 'snapGrid' - | 'translateExtent' - | 'connectOnClick' - | 'defaultEdgeOptions' - | 'fitView' - | 'fitViewOptions' - | 'onNodesDelete' - | 'onEdgesDelete' - | 'onDelete' - | 'onNodeDragStart' - | 'onNodeDrag' - | 'onNodeDragStop' - | 'onSelectionDragStart' - | 'onSelectionDrag' - | 'onSelectionDragStop' - | 'onMove' - | 'onMoveStart' - | 'onMoveEnd' - | 'noPanClassName' - | 'nodeOrigin' - | 'elevateNodesOnSelect' - | 'autoPanOnConnect' - | 'autoPanOnNodeDrag' - | 'onError' - | 'connectionRadius' - | 'isValidConnection' - | 'selectNodesOnDrag' - | 'nodeDragThreshold' -> & { rfId: string }; +// these fields exist in the global store and we need to keep them up to date +const reactFlowFieldsToTrack = [ + 'nodes', + 'edges', + 'defaultNodes', + 'defaultEdges', + 'onConnect', + 'onConnectStart', + 'onConnectEnd', + 'onClickConnectStart', + 'onClickConnectEnd', + 'nodesDraggable', + 'nodesConnectable', + 'nodesFocusable', + 'edgesFocusable', + 'edgesUpdatable', + 'elevateNodesOnSelect', + 'minZoom', + 'maxZoom', + 'nodeExtent', + 'onNodesChange', + 'onEdgesChange', + 'elementsSelectable', + 'connectionMode', + 'snapGrid', + 'snapToGrid', + 'translateExtent', + 'connectOnClick', + 'defaultEdgeOptions', + 'fitView', + 'fitViewOptions', + 'onNodesDelete', + 'onEdgesDelete', + 'onDelete', + 'onNodeDrag', + 'onNodeDragStart', + 'onNodeDragStop', + 'onSelectionDrag', + 'onSelectionDragStart', + 'onSelectionDragStop', + 'onMoveStart', + 'onMove', + 'onMoveEnd', + 'noPanClassName', + 'nodeOrigin', + 'autoPanOnConnect', + 'autoPanOnNodeDrag', + 'onError', + 'connectionRadius', + 'isValidConnection', + 'selectNodesOnDrag', + 'nodeDragThreshold', +] as const; + +type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; +type StoreUpdaterProps = Pick & { rfId: string }; + +// rfId doesn't exist in ReactFlowProps, but it's one of the fields we want to update +const fieldsToTrack = [...reactFlowFieldsToTrack, 'rfId'] as const; const selector = (s: ReactFlowState) => ({ setNodes: s.setNodes, @@ -76,80 +81,7 @@ const selector = (s: ReactFlowState) => ({ reset: s.reset, }); -function useStoreUpdater(value: T | undefined, setStoreAction: (param: T) => void) { - useEffect(() => { - if (typeof value !== 'undefined') { - setStoreAction(value); - } - }, [value]); -} - -// updates with values in store that don't have a dedicated setter function -function useDirectStoreUpdater( - key: keyof ReactFlowStore, - value: unknown, - setState: StoreApi['setState'] -) { - useEffect(() => { - if (typeof value !== 'undefined') { - setState({ [key]: value }); - } - }, [value]); -} - -const StoreUpdater = ({ - nodes, - edges, - defaultNodes, - defaultEdges, - onConnect, - onConnectStart, - onConnectEnd, - onClickConnectStart, - onClickConnectEnd, - nodesDraggable, - nodesConnectable, - nodesFocusable, - edgesFocusable, - edgesUpdatable, - elevateNodesOnSelect, - minZoom, - maxZoom, - nodeExtent, - onNodesChange, - onEdgesChange, - elementsSelectable, - connectionMode, - snapGrid, - snapToGrid, - translateExtent, - connectOnClick, - defaultEdgeOptions, - fitView, - fitViewOptions, - onNodesDelete, - onEdgesDelete, - onDelete, - onNodeDrag, - onNodeDragStart, - onNodeDragStop, - onSelectionDrag, - onSelectionDragStart, - onSelectionDragStop, - onMoveStart, - onMove, - onMoveEnd, - noPanClassName, - nodeOrigin, - rfId, - autoPanOnConnect, - autoPanOnNodeDrag, - onError, - connectionRadius, - isValidConnection, - selectNodesOnDrag, - nodeDragThreshold, -}: StoreUpdaterProps) => { +const StoreUpdater = (props: StoreUpdaterProps) => { const { setNodes, setEdges, @@ -163,64 +95,44 @@ const StoreUpdater = ({ const store = useStoreApi(); useEffect(() => { - const edgesWithDefaults = defaultEdges?.map((e) => ({ ...e, ...defaultEdgeOptions })); - setDefaultNodesAndEdges(defaultNodes, edgesWithDefaults); + const edgesWithDefaults = props.defaultEdges?.map((e) => ({ ...e, ...props.defaultEdgeOptions })); + setDefaultNodesAndEdges(props.defaultNodes, edgesWithDefaults); return () => { reset(); }; }, []); - useDirectStoreUpdater('defaultEdgeOptions', defaultEdgeOptions, store.setState); - useDirectStoreUpdater('connectionMode', connectionMode, store.setState); - useDirectStoreUpdater('onConnect', onConnect, store.setState); - useDirectStoreUpdater('onConnectStart', onConnectStart, store.setState); - useDirectStoreUpdater('onConnectEnd', onConnectEnd, store.setState); - useDirectStoreUpdater('onClickConnectStart', onClickConnectStart, store.setState); - useDirectStoreUpdater('onClickConnectEnd', onClickConnectEnd, store.setState); - useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState); - useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState); - useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState); - useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState); - useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState); - useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState); - useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState); - useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState); - useDirectStoreUpdater('snapGrid', snapGrid, store.setState); - useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState); - useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState); - useDirectStoreUpdater('connectOnClick', connectOnClick, store.setState); - useDirectStoreUpdater('fitViewOnInit', fitView, store.setState); - useDirectStoreUpdater('fitViewOnInitOptions', fitViewOptions, store.setState); - useDirectStoreUpdater('onNodesDelete', onNodesDelete, store.setState); - useDirectStoreUpdater('onEdgesDelete', onEdgesDelete, store.setState); - useDirectStoreUpdater('onDelete', onDelete, store.setState); - useDirectStoreUpdater('onNodeDrag', onNodeDrag, store.setState); - useDirectStoreUpdater('onNodeDragStart', onNodeDragStart, store.setState); - useDirectStoreUpdater('onNodeDragStop', onNodeDragStop, store.setState); - useDirectStoreUpdater('onSelectionDrag', onSelectionDrag, store.setState); - useDirectStoreUpdater('onSelectionDragStart', onSelectionDragStart, store.setState); - useDirectStoreUpdater('onSelectionDragStop', onSelectionDragStop, store.setState); - useDirectStoreUpdater('onMove', onMove, store.setState); - useDirectStoreUpdater('onMoveStart', onMoveStart, store.setState); - useDirectStoreUpdater('onMoveEnd', onMoveEnd, store.setState); - useDirectStoreUpdater('noPanClassName', noPanClassName, store.setState); - useDirectStoreUpdater('nodeOrigin', nodeOrigin, store.setState); - useDirectStoreUpdater('rfId', rfId, store.setState); - useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState); - useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState); - useDirectStoreUpdater('onError', onError || devWarn, store.setState); - useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState); - useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState); - useDirectStoreUpdater('selectNodesOnDrag', selectNodesOnDrag, store.setState); - useDirectStoreUpdater('nodeDragThreshold', nodeDragThreshold, store.setState); + const previousFields = useRef>({}); - useStoreUpdater(nodes, setNodes); - useStoreUpdater(edges, setEdges); - useStoreUpdater(minZoom, setMinZoom); - useStoreUpdater(maxZoom, setMaxZoom); - useStoreUpdater(translateExtent, setTranslateExtent); - useStoreUpdater(nodeExtent, setNodeExtent); + useEffect( + () => { + for (const fieldName of fieldsToTrack) { + const fieldValue = props[fieldName]; + const previousFieldValue = previousFields.current[fieldName]; + + if (fieldValue === previousFieldValue) continue; + if (typeof props[fieldName] === 'undefined') continue; + + // Custom handling with dedicated setters for some fields + if (fieldName === 'nodes') setNodes(fieldValue as Node[]); + else if (fieldName === 'edges') setEdges(fieldValue as Edge[]); + else if (fieldName === 'minZoom') setMinZoom(fieldValue as number); + else if (fieldName === 'maxZoom') setMaxZoom(fieldValue as number); + else if (fieldName === 'translateExtent') setTranslateExtent(fieldValue as CoordinateExtent); + else if (fieldName === 'nodeExtent') setNodeExtent(fieldValue as CoordinateExtent); + // Renamed fields + else if (fieldName === 'fitView') store.setState({ fitViewOnInit: fieldValue as boolean }); + else if (fieldName === 'fitViewOptions') store.setState({ fitViewOnInitOptions: fieldValue as FitViewOptions }); + // General case + else store.setState({ [fieldName]: fieldValue }); + } + + previousFields.current = props; + }, + // Only re-run the effect if one of the fields we track changes + fieldsToTrack.map((fieldName) => props[fieldName]) + ); return null; };