Merge pull request #3729 from xyflow/refactor/store-updater
Refactor: StoreUpdater
This commit is contained in:
@@ -7,7 +7,7 @@ const sourceHandleStyleB: CSSProperties = {
|
|||||||
left: 'auto',
|
left: 'auto',
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomNode: FC<NodeProps> = ({ data, positionAbsolute }) => {
|
const CustomNode: FC<NodeProps> = ({ data, positionAbsoluteX, positionAbsoluteY }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type="target" position={Position.Top} />
|
<Handle type="target" position={Position.Top} />
|
||||||
@@ -18,7 +18,7 @@ const CustomNode: FC<NodeProps> = ({ data, positionAbsolute }) => {
|
|||||||
<div>
|
<div>
|
||||||
Position:{' '}
|
Position:{' '}
|
||||||
<strong>
|
<strong>
|
||||||
{positionAbsolute.x.toFixed(2)},{positionAbsolute.y.toFixed(2)}
|
{positionAbsoluteX.toFixed(2)},{positionAbsoluteY.toFixed(2)}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,67 +3,72 @@
|
|||||||
* We distinguish between values we can update directly with `useDirectStoreUpdater` (like `snapGrid`)
|
* 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`).
|
* and values that have a dedicated setter function in the store (like `setNodes`).
|
||||||
*/
|
*/
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { StoreApi } from 'zustand';
|
|
||||||
import { shallow } from 'zustand/shallow';
|
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 { 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<
|
// these fields exist in the global store and we need to keep them up to date
|
||||||
ReactFlowProps,
|
const reactFlowFieldsToTrack = [
|
||||||
| 'nodes'
|
'nodes',
|
||||||
| 'edges'
|
'edges',
|
||||||
| 'defaultNodes'
|
'defaultNodes',
|
||||||
| 'defaultEdges'
|
'defaultEdges',
|
||||||
| 'onConnect'
|
'onConnect',
|
||||||
| 'onConnectStart'
|
'onConnectStart',
|
||||||
| 'onConnectEnd'
|
'onConnectEnd',
|
||||||
| 'onClickConnectStart'
|
'onClickConnectStart',
|
||||||
| 'onClickConnectEnd'
|
'onClickConnectEnd',
|
||||||
| 'nodesDraggable'
|
'nodesDraggable',
|
||||||
| 'nodesConnectable'
|
'nodesConnectable',
|
||||||
| 'nodesFocusable'
|
'nodesFocusable',
|
||||||
| 'edgesFocusable'
|
'edgesFocusable',
|
||||||
| 'edgesUpdatable'
|
'edgesUpdatable',
|
||||||
| 'minZoom'
|
'elevateNodesOnSelect',
|
||||||
| 'maxZoom'
|
'minZoom',
|
||||||
| 'nodeExtent'
|
'maxZoom',
|
||||||
| 'onNodesChange'
|
'nodeExtent',
|
||||||
| 'onEdgesChange'
|
'onNodesChange',
|
||||||
| 'elementsSelectable'
|
'onEdgesChange',
|
||||||
| 'connectionMode'
|
'elementsSelectable',
|
||||||
| 'snapToGrid'
|
'connectionMode',
|
||||||
| 'snapGrid'
|
'snapGrid',
|
||||||
| 'translateExtent'
|
'snapToGrid',
|
||||||
| 'connectOnClick'
|
'translateExtent',
|
||||||
| 'defaultEdgeOptions'
|
'connectOnClick',
|
||||||
| 'fitView'
|
'defaultEdgeOptions',
|
||||||
| 'fitViewOptions'
|
'fitView',
|
||||||
| 'onNodesDelete'
|
'fitViewOptions',
|
||||||
| 'onEdgesDelete'
|
'onNodesDelete',
|
||||||
| 'onDelete'
|
'onEdgesDelete',
|
||||||
| 'onNodeDragStart'
|
'onDelete',
|
||||||
| 'onNodeDrag'
|
'onNodeDrag',
|
||||||
| 'onNodeDragStop'
|
'onNodeDragStart',
|
||||||
| 'onSelectionDragStart'
|
'onNodeDragStop',
|
||||||
| 'onSelectionDrag'
|
'onSelectionDrag',
|
||||||
| 'onSelectionDragStop'
|
'onSelectionDragStart',
|
||||||
| 'onMove'
|
'onSelectionDragStop',
|
||||||
| 'onMoveStart'
|
'onMoveStart',
|
||||||
| 'onMoveEnd'
|
'onMove',
|
||||||
| 'noPanClassName'
|
'onMoveEnd',
|
||||||
| 'nodeOrigin'
|
'noPanClassName',
|
||||||
| 'elevateNodesOnSelect'
|
'nodeOrigin',
|
||||||
| 'autoPanOnConnect'
|
'autoPanOnConnect',
|
||||||
| 'autoPanOnNodeDrag'
|
'autoPanOnNodeDrag',
|
||||||
| 'onError'
|
'onError',
|
||||||
| 'connectionRadius'
|
'connectionRadius',
|
||||||
| 'isValidConnection'
|
'isValidConnection',
|
||||||
| 'selectNodesOnDrag'
|
'selectNodesOnDrag',
|
||||||
| 'nodeDragThreshold'
|
'nodeDragThreshold',
|
||||||
> & { rfId: string };
|
] as const;
|
||||||
|
|
||||||
|
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
|
||||||
|
type StoreUpdaterProps = Pick<ReactFlowProps, ReactFlowFieldsToTrack> & { 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) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
setNodes: s.setNodes,
|
setNodes: s.setNodes,
|
||||||
@@ -76,80 +81,7 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
reset: s.reset,
|
reset: s.reset,
|
||||||
});
|
});
|
||||||
|
|
||||||
function useStoreUpdater<T>(value: T | undefined, setStoreAction: (param: T) => void) {
|
const StoreUpdater = (props: StoreUpdaterProps) => {
|
||||||
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<ReactFlowState>['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 {
|
const {
|
||||||
setNodes,
|
setNodes,
|
||||||
setEdges,
|
setEdges,
|
||||||
@@ -163,64 +95,44 @@ const StoreUpdater = ({
|
|||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const edgesWithDefaults = defaultEdges?.map((e) => ({ ...e, ...defaultEdgeOptions }));
|
const edgesWithDefaults = props.defaultEdges?.map((e) => ({ ...e, ...props.defaultEdgeOptions }));
|
||||||
setDefaultNodesAndEdges(defaultNodes, edgesWithDefaults);
|
setDefaultNodesAndEdges(props.defaultNodes, edgesWithDefaults);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useDirectStoreUpdater('defaultEdgeOptions', defaultEdgeOptions, store.setState);
|
const previousFields = useRef<Partial<StoreUpdaterProps>>({});
|
||||||
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);
|
|
||||||
|
|
||||||
useStoreUpdater<Node[]>(nodes, setNodes);
|
useEffect(
|
||||||
useStoreUpdater<Edge[]>(edges, setEdges);
|
() => {
|
||||||
useStoreUpdater<number>(minZoom, setMinZoom);
|
for (const fieldName of fieldsToTrack) {
|
||||||
useStoreUpdater<number>(maxZoom, setMaxZoom);
|
const fieldValue = props[fieldName];
|
||||||
useStoreUpdater<CoordinateExtent>(translateExtent, setTranslateExtent);
|
const previousFieldValue = previousFields.current[fieldName];
|
||||||
useStoreUpdater<CoordinateExtent>(nodeExtent, setNodeExtent);
|
|
||||||
|
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;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user