From 3a1a365a63fc4564d9a8d96309908986fcc86f95 Mon Sep 17 00:00:00 2001 From: moklick Date: Sun, 9 Oct 2022 20:39:27 +0200 Subject: [PATCH 1/2] fix(useOnSelectionChange): repair hook closes #2484 --- examples/vite-app/src/App/index.tsx | 6 ++ .../UseOnSelectionChange/CustomNode.tsx | 36 ++++++++++++ .../examples/UseOnSelectionChange/index.tsx | 58 +++++++++++++++++++ .../components/SelectionListener/index.tsx | 42 +++++++++----- .../core/src/container/ReactFlow/index.tsx | 2 +- 5 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 examples/vite-app/src/examples/UseOnSelectionChange/CustomNode.tsx create mode 100644 examples/vite-app/src/examples/UseOnSelectionChange/index.tsx diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 0ec2a021..90339935 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -36,6 +36,7 @@ import Validation from '../examples/Validation'; import UseKeyPress from '../examples/UseKeyPress'; import EdgeRouting from '../examples/EdgeRouting'; import CancelConnection from '../examples/CancelConnection'; +import UseOnSelectionChange from '../examples/UseOnSelectionChange'; interface IRoute { name: string; @@ -199,6 +200,11 @@ const routes: IRoute[] = [ path: '/update-node', component: UpdateNode, }, + { + name: 'useOnSelectionChange', + path: '/use-on-selection-change', + component: UseOnSelectionChange, + }, { name: 'useReactFlow', path: '/usereactflow', diff --git a/examples/vite-app/src/examples/UseOnSelectionChange/CustomNode.tsx b/examples/vite-app/src/examples/UseOnSelectionChange/CustomNode.tsx new file mode 100644 index 00000000..1684656b --- /dev/null +++ b/examples/vite-app/src/examples/UseOnSelectionChange/CustomNode.tsx @@ -0,0 +1,36 @@ +import React, { useState, memo, FC, useMemo, CSSProperties } from 'react'; +import { Handle, Position, NodeProps, useUpdateNodeInternals } from 'reactflow'; + +const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' }; + +const CustomNode: FC = ({ id }) => { + const [handleCount, setHandleCount] = useState(1); + const updateNodeInternals = useUpdateNodeInternals(); + + const handles = useMemo( + () => + Array.from({ length: handleCount }, (x, i) => { + const handleId = `handle-${i}`; + return ; + }), + [handleCount] + ); + + return ( +
+ +
output handle count: {handleCount}
+ + {handles} +
+ ); +}; + +export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx b/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx new file mode 100644 index 00000000..160ca769 --- /dev/null +++ b/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react'; +import ReactFlow, { + addEdge, + ReactFlowProvider, + Node, + Connection, + Edge, + useNodesState, + useEdgesState, + useOnSelectionChange, + OnSelectionChangeParams, +} from 'reactflow'; + +const initialNodes: Node[] = [ + { + id: '1', + type: 'default', + data: { label: 'Node 1' }, + position: { x: 250, y: 5 }, + }, +]; + +const SelectionLogger = () => { + const onChange = useCallback(({ nodes, edges }: OnSelectionChangeParams) => { + console.log(nodes, edges); + }, []); + + useOnSelectionChange({ + onChange, + }); + + return null; +}; + +const Flow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); + + return ( + + ); +}; + +const WrappedFlow = () => ( + + + + +); + +export default WrappedFlow; diff --git a/packages/core/src/components/SelectionListener/index.tsx b/packages/core/src/components/SelectionListener/index.tsx index 9fdcc31b..5655fc6f 100644 --- a/packages/core/src/components/SelectionListener/index.tsx +++ b/packages/core/src/components/SelectionListener/index.tsx @@ -4,9 +4,9 @@ import shallow from 'zustand/shallow'; import { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types'; import { useStore, useStoreApi } from '../../hooks/useStore'; -interface SelectionListenerProps { - onSelectionChange: OnSelectionChangeFunc; -} +type SelectionListenerProps = { + onSelectionChange?: OnSelectionChangeFunc; +}; const selector = (s: ReactFlowState) => ({ selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected), @@ -15,30 +15,42 @@ const selector = (s: ReactFlowState) => ({ type SelectorSlice = ReturnType; -function areEqual(objA: SelectorSlice, objB: SelectorSlice) { - const selectedNodeIdsA = objA.selectedNodes.map((n: Node) => n.id); - const selectedNodeIdsB = objB.selectedNodes.map((n: Node) => n.id); +const selectId = (obj: Node | Edge) => obj.id; - const selectedEdgeIdsA = objA.selectedEdges.map((e: Edge) => e.id); - const selectedEdgeIdsB = objB.selectedEdges.map((e: Edge) => e.id); - - return shallow(selectedNodeIdsA, selectedNodeIdsB) && shallow(selectedEdgeIdsA, selectedEdgeIdsB); +function areEqual(a: SelectorSlice, b: SelectorSlice) { + return ( + shallow(a.selectedNodes.map(selectId), b.selectedNodes.map(selectId)) && + shallow(a.selectedEdges.map(selectId), b.selectedEdges.map(selectId)) + ); } // This is just a helper component for calling the onSelectionChange listener. // @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component? -function SelectionListener({ onSelectionChange }: SelectionListenerProps) { +const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) => { const store = useStoreApi(); const { selectedNodes, selectedEdges } = useStore(selector, areEqual); useEffect(() => { const params = { nodes: selectedNodes, edges: selectedEdges }; - - onSelectionChange(params); + onSelectionChange?.(params); store.getState().onSelectionChange?.(params); - }, [selectedNodes, selectedEdges]); + }, [selectedNodes, selectedEdges, onSelectionChange]); + + return null; +}); + +SelectionListener.displayName = 'SelectionListener'; + +const changeSelector = (s: ReactFlowState) => !!s.onSelectionChange; + +function Wrapper({ onSelectionChange }: SelectionListenerProps) { + const storeHasSelectionChange = useStore(changeSelector); + + if (onSelectionChange || storeHasSelectionChange) { + return ; + } return null; } -export default memo(SelectionListener); +export default Wrapper; diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index bd493eec..d83aea6c 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -275,7 +275,7 @@ const ReactFlow = forwardRef( nodeOrigin={nodeOrigin} id={id} /> - {onSelectionChange && } + {children} {!disableKeyboardA11y && } From 95132c44f7f4f5b2ab66483c7097ccd13aebdf91 Mon Sep 17 00:00:00 2001 From: moklick Date: Sun, 9 Oct 2022 20:44:05 +0200 Subject: [PATCH 2/2] chore(changeset): add --- .changeset/smooth-rabbits-own.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/smooth-rabbits-own.md diff --git a/.changeset/smooth-rabbits-own.md b/.changeset/smooth-rabbits-own.md new file mode 100644 index 00000000..4e85067a --- /dev/null +++ b/.changeset/smooth-rabbits-own.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': patch +--- + +repair useOnSelectionChange hook