refactor(state): replace redux with zustand

This commit is contained in:
moklick
2021-10-13 12:54:01 +02:00
parent e01249a87c
commit ee0d29029a
38 changed files with 786 additions and 896 deletions
+5 -2
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useState, CSSProperties } from 'react';
import { useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import {
@@ -12,6 +12,7 @@ import {
ConnectionLineType,
ConnectionLineComponent,
HandleType,
ReactFlowState,
} from '../../types';
interface ConnectionLineProps {
@@ -27,6 +28,8 @@ interface ConnectionLineProps {
CustomConnectionLineComponent?: ConnectionLineComponent;
}
const nodesSelector = (s: ReactFlowState) => s.nodes;
export default ({
connectionNodeId,
connectionHandleId,
@@ -39,7 +42,7 @@ export default ({
isConnectable,
CustomConnectionLineComponent,
}: ConnectionLineProps) => {
const nodes = useStoreState((state) => state.nodes);
const nodes = useStore(nodesSelector);
const [sourceNode, setSourceNode] = useState<Node | null>(null);
const nodeId = connectionNodeId;
const handleId = connectionHandleId;
-1
View File
@@ -1,7 +1,6 @@
import React, { memo } from 'react';
import EdgeText from './EdgeText';
import { getMarkerEnd, getCenter } from './utils';
import { EdgeProps, Position } from '../../types';
+28 -10
View File
@@ -1,11 +1,19 @@
import React, { memo, ComponentType, useCallback, useState, useMemo } from 'react';
import cc from 'classcat';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { Edge, EdgeProps, WrapEdgeProps } from '../../types';
import { useStore, useStoreApi } from '../../store';
import { Edge, EdgeProps, WrapEdgeProps, ReactFlowState } from '../../types';
import { onMouseDown } from '../../components/Handle/handler';
import { EdgeAnchor } from './EdgeAnchor';
const selector = (s: ReactFlowState) => ({
addSelectedElements: s.addSelectedElements,
setConnectionNodeId: s.setConnectionNodeId,
unsetNodesSelection: s.unsetNodesSelection,
setPosition: s.setConnectionPosition,
connectionMode: s.connectionMode,
});
export default (EdgeComponent: ComponentType<EdgeProps>) => {
const EdgeWrapper = ({
id,
@@ -47,11 +55,9 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
onEdgeUpdateStart,
onEdgeUpdateEnd,
}: WrapEdgeProps): JSX.Element | null => {
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
const connectionMode = useStoreState((state) => state.connectionMode);
const store = useStoreApi();
const { addSelectedElements, setConnectionNodeId, unsetNodesSelection, setPosition, connectionMode } =
useStore(selector);
const [updating, setUpdating] = useState<boolean>(false);
@@ -90,7 +96,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
if (elementsSelectable) {
unsetNodesSelection();
addSelectedElements(edgeElement);
addSelectedElements([edgeElement]);
}
onClick?.(event, edgeElement);
@@ -157,10 +163,22 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
isValidConnection,
connectionMode,
isSourceHandle ? 'target' : 'source',
_onEdgeUpdate
_onEdgeUpdate,
store.getState
);
},
[id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition, edgeElement, onConnectEdge]
[
id,
source,
target,
type,
sourceHandleId,
targetHandleId,
setConnectionNodeId,
setPosition,
edgeElement,
onConnectEdge,
]
);
const onEdgeUpdaterSourceMouseDown = useCallback(
+8 -4
View File
@@ -1,16 +1,20 @@
import { useEffect } from 'react';
import { useStoreActions } from '../../store/hooks';
import { Node, Edge } from '../../types';
import { useStore } from '../../store';
import { Node, Edge, ReactFlowState } from '../../types';
interface ElementUpdaterProps {
nodes: Node[];
edges: Edge[];
}
const selector = (s: ReactFlowState) => ({
setNodes: s.setNodes,
setEdges: s.setEdges,
});
const ElementUpdater = ({ nodes, edges }: ElementUpdaterProps) => {
const setNodes = useStoreActions((actions) => actions.setNodes);
const setEdges = useStoreActions((actions) => actions.setEdges);
const { setNodes, setEdges } = useStore(selector);
useEffect(() => {
setNodes(nodes);
+4 -4
View File
@@ -1,9 +1,8 @@
import { MouseEvent as ReactMouseEvent } from 'react';
import { Store } from 'redux';
import { GetState } from 'zustand';
import { getHostForElement } from '../../utils';
import { ReactFlowState } from '../../types';
import { ReactFlowAction } from '../../store/actions';
import {
ElementId,
@@ -107,7 +106,7 @@ export function onMouseDown(
onConnectStart?: OnConnectStartFunc,
onConnectStop?: OnConnectStopFunc,
onConnectEnd?: OnConnectEndFunc,
store?: Store<ReactFlowState, ReactFlowAction>
getState?: GetState<ReactFlowState>
): void {
const reactFlowNode = (event.target as Element).closest('.react-flow');
// when react-flow is used inside a shadow root we can't use document
@@ -181,7 +180,8 @@ export function onMouseDown(
onConnectStop?.(event);
if (isValid) {
onConnect?.(connection, store?.getState().nodes || []);
const nodes = getState?.().nodes;
onConnect?.(connection, nodes || []);
}
onConnectEnd?.(event);
+23 -12
View File
@@ -1,9 +1,9 @@
import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from 'react';
import cc from 'classcat';
import { useStoreActions, useStoreState, useStore } from '../../store/hooks';
import { useStore } from '../../store';
import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, Connection, ElementId, Position, Node } from '../../types';
import { HandleProps, Connection, ElementId, Position, Node, ReactFlowState } from '../../types';
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
@@ -11,6 +11,16 @@ const alwaysValid = () => true;
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
const selector = (s: ReactFlowState) => ({
setPosition: s.setConnectionPosition,
setConnectionNodeId: s.setConnectionNodeId,
onConnectAction: s.onConnect,
onConnectStart: s.onConnectStart,
onConnectStop: s.onConnectStop,
onConnectEnd: s.onConnectEnd,
connectionMode: s.connectionMode,
});
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
(
{
@@ -26,15 +36,17 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
},
ref
) => {
const store = useStore();
const nodeId = useContext(NodeIdContext) as ElementId;
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
const onConnectAction = useStoreState((state) => state.onConnect);
const onConnectStart = useStoreState((state) => state.onConnectStart);
const onConnectStop = useStoreState((state) => state.onConnectStop);
const onConnectEnd = useStoreState((state) => state.onConnectEnd);
const connectionMode = useStoreState((state) => state.connectionMode);
const {
setPosition,
setConnectionNodeId,
onConnectAction,
onConnectStart,
onConnectStop,
onConnectEnd,
connectionMode,
} = useStore(selector);
const handleId = id || null;
const isTarget = type === 'target';
@@ -62,8 +74,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
undefined,
onConnectStart,
onConnectStop,
onConnectEnd,
store
onConnectEnd
);
},
[
+9 -7
View File
@@ -8,13 +8,15 @@ const DefaultNode = ({
isConnectable,
targetPosition = Position.Top,
sourcePosition = Position.Bottom,
}: NodeProps) => (
<>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
);
}: NodeProps) => {
return (
<>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
);
};
DefaultNode.displayName = 'DefaultNode';
+14 -16
View File
@@ -13,7 +13,7 @@ export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
export const getHandleBoundsByHandleType = (
selector: string,
nodeElement: HTMLDivElement,
parentBounds: ClientRect | DOMRect,
parentBounds: DOMRect,
k: number
): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector);
@@ -24,20 +24,18 @@ export const getHandleBoundsByHandleType = (
const handlesArray = Array.from(handles) as HTMLDivElement[];
return handlesArray.map(
(handle): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const handleId = handle.getAttribute('data-handleid');
const handlePosition = (handle.getAttribute('data-handlepos') as unknown) as Position;
return handlesArray.map((handle): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const handleId = handle.getAttribute('data-handleid');
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) / k,
y: (bounds.top - parentBounds.top) / k,
...dimensions,
};
}
);
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) / k,
y: (bounds.top - parentBounds.top) / k,
...dimensions,
};
});
};
+12 -9
View File
@@ -2,9 +2,15 @@ import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo,
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
import cc from 'classcat';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import { Provider } from '../../contexts/NodeIdContext';
import { NodeComponentProps, WrapNodeProps } from '../../types';
import { NodeComponentProps, WrapNodeProps, ReactFlowState } from '../../types';
const selector = (s: ReactFlowState) => ({
addSelectedElements: s.addSelectedElements,
onNodesChange: s.onNodesChange,
unsetNodesSelection: s.unsetNodesSelection,
});
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = ({
@@ -41,10 +47,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
dragHandle,
}: WrapNodeProps) => {
// const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
const onNodesChange = useStoreState((state) => state.onNodesChange);
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const { addSelectedElements, onNodesChange, unsetNodesSelection } = useStore(selector);
const nodeElement = useRef<HTMLDivElement>(null);
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
@@ -114,7 +117,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
unsetNodesSelection();
if (!selected) {
addSelectedElements(node);
addSelectedElements([node]);
}
}
@@ -132,7 +135,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
unsetNodesSelection();
if (!selected) {
addSelectedElements(node);
addSelectedElements([node]);
}
} else if (!selectNodesOnDrag && !selected && isSelectable) {
unsetNodesSelection();
@@ -173,7 +176,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
// Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!isDragging) {
if (isSelectable && !selectNodesOnDrag && !selected) {
addSelectedElements(node);
addSelectedElements([node]);
}
onClick?.(event as MouseEvent, node);
+24 -11
View File
@@ -6,9 +6,9 @@
import React, { useMemo, useCallback, useRef, MouseEvent } from 'react';
import ReactDraggable, { DraggableData } from 'react-draggable';
import { useStoreState, useStoreActions } from '../../store/hooks';
import { useStore } from '../../store';
import { isNode } from '../../utils/graph';
import { Node } from '../../types';
import { Node, ReactFlowState } from '../../types';
export interface NodesSelectionProps {
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
@@ -17,21 +17,34 @@ export interface NodesSelectionProps {
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
}
const selector = (s: ReactFlowState) => ({
transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox,
selectionActive: s.selectionActive,
selectedElements: s.selectedElements,
snapToGrid: s.snapToGrid,
snapGrid: s.snapGrid,
nodes: s.nodes,
updateNodePosDiff: s.updateNodePosDiff,
});
export default ({
onSelectionDragStart,
onSelectionDrag,
onSelectionDragStop,
onSelectionContextMenu,
}: NodesSelectionProps) => {
const [tX, tY, tScale] = useStoreState((state) => state.transform);
const selectedNodesBbox = useStoreState((state) => state.selectedNodesBbox);
const selectionActive = useStoreState((state) => state.selectionActive);
const selectedElements = useStoreState((state) => state.selectedElements);
const snapToGrid = useStoreState((state) => state.snapToGrid);
const snapGrid = useStoreState((state) => state.snapGrid);
const nodes = useStoreState((state) => state.nodes);
const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff);
const {
transform,
selectedNodesBbox,
selectionActive,
selectedElements,
snapToGrid,
snapGrid,
nodes,
updateNodePosDiff,
} = useStore(selector);
const [tX, tY, tScale] = transform;
const nodeRef = useRef(null);
+5 -5
View File
@@ -1,18 +1,18 @@
import { useEffect } from 'react';
import { Elements } from '../../types';
import { useStoreState } from '../../store/hooks';
import { Elements, ReactFlowState } from '../../types';
import { useStore } from '../../store';
interface SelectionListenerProps {
onSelectionChange: (elements: Elements | null) => void;
}
const selectedElementsSelector = (s: ReactFlowState) => s.selectedElements;
// This is just a helper component for calling the onSelectionChange listener.
// As soon as easy-peasy has implemented the effectOn hook, we can remove this component
// and use the hook instead. https://github.com/ctrlplusb/easy-peasy/pull/459
export default ({ onSelectionChange }: SelectionListenerProps) => {
const selectedElements = useStoreState((s) => s.selectedElements);
const selectedElements = useStore(selectedElementsSelector);
useEffect(() => {
onSelectionChange(selectedElements);
+22 -9
View File
@@ -4,8 +4,8 @@
import React, { memo } from 'react';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { XYPosition } from '../../types';
import { useStore } from '../../store';
import { XYPosition, ReactFlowState } from '../../types';
type UserSelectionProps = {
selectionKeyPressed: boolean;
@@ -25,8 +25,10 @@ function getMousePosition(event: React.MouseEvent): XYPosition | void {
};
}
const userSelectionRectSelector = (state: ReactFlowState) => state.userSelectionRect;
const SelectionRect = () => {
const userSelectionRect = useStoreState((state) => state.userSelectionRect);
const userSelectionRect = useStore(userSelectionRectSelector);
if (!userSelectionRect.draw) {
return null;
@@ -44,14 +46,25 @@ const SelectionRect = () => {
);
};
const selector = (s: ReactFlowState) => ({
selectionActive: s.selectionActive,
elementsSelectable: s.elementsSelectable,
setUserSelection: s.setUserSelection,
updateUserSelection: s.updateUserSelection,
unsetUserSelection: s.unsetUserSelection,
unsetNodesSelection: s.unsetNodesSelection,
});
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
const selectionActive = useStoreState((state) => state.selectionActive);
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const {
selectionActive,
elementsSelectable,
setUserSelection,
updateUserSelection,
unsetUserSelection,
unsetNodesSelection,
} = useStore(selector);
const setUserSelection = useStoreActions((actions) => actions.setUserSelection);
const updateUserSelection = useStoreActions((actions) => actions.updateUserSelection);
const unsetUserSelection = useStoreActions((actions) => actions.unsetUserSelection);
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const renderUserSelectionPane = selectionActive || selectionKeyPressed;
if (!elementsSelectable || !renderUserSelectionPane) {