From c9d079ddc3fc9e9ec68aa5066338b9aa50108746 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 16 May 2023 14:54:12 +0200 Subject: [PATCH 1/9] fix(updateNodeInternals): update type --- .../vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx | 2 +- packages/core/src/types/general.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx b/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx index 1684656b..1b25bdc1 100644 --- a/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx +++ b/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx @@ -1,4 +1,4 @@ -import React, { useState, memo, FC, useMemo, CSSProperties } from 'react'; +import { useState, memo, FC, useMemo, CSSProperties } from 'react'; import { Handle, Position, NodeProps, useUpdateNodeInternals } from 'reactflow'; const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' }; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index 9803d4a0..d6c2e33b 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -257,7 +257,7 @@ export type ReactFlowActions = { export type ReactFlowState = ReactFlowStore & ReactFlowActions; -export type UpdateNodeInternals = (nodeId: string) => void; +export type UpdateNodeInternals = (nodeId: string | string[]) => void; export type OnSelectionChangeParams = { nodes: Node[]; From 4374459ef9fec797bbc0407231f09a1acacd245b Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 16 May 2023 14:55:03 +0200 Subject: [PATCH 2/9] chore(changeset): add --- .changeset/wise-needles-kneel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/wise-needles-kneel.md diff --git a/.changeset/wise-needles-kneel.md b/.changeset/wise-needles-kneel.md new file mode 100644 index 00000000..5deec8b6 --- /dev/null +++ b/.changeset/wise-needles-kneel.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': patch +--- + +fix useUpdateNodeInternals type From 005df0da114ebbd9f3c6c2622f47c6a054036a81 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 16 May 2023 15:07:02 +0200 Subject: [PATCH 3/9] fix(useNodes,useEdges): prevent re-renderings --- packages/core/src/hooks/useEdges.ts | 4 +++- packages/core/src/hooks/useNodes.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/src/hooks/useEdges.ts b/packages/core/src/hooks/useEdges.ts index 78c0e36a..194f8805 100644 --- a/packages/core/src/hooks/useEdges.ts +++ b/packages/core/src/hooks/useEdges.ts @@ -1,10 +1,12 @@ +import { shallow } from 'zustand/shallow'; + import { useStore } from '../hooks/useStore'; import type { Edge, ReactFlowState } from '../types'; const edgesSelector = (state: ReactFlowState) => state.edges; function useEdges(): Edge[] { - const edges = useStore(edgesSelector); + const edges = useStore(edgesSelector, shallow); return edges; } diff --git a/packages/core/src/hooks/useNodes.ts b/packages/core/src/hooks/useNodes.ts index 8681b4fd..8d71b435 100644 --- a/packages/core/src/hooks/useNodes.ts +++ b/packages/core/src/hooks/useNodes.ts @@ -1,10 +1,12 @@ +import { shallow } from 'zustand/shallow'; + import { useStore } from '../hooks/useStore'; import type { Node, ReactFlowState } from '../types'; const nodesSelector = (state: ReactFlowState) => state.getNodes(); function useNodes(): Node[] { - const nodes = useStore(nodesSelector); + const nodes = useStore(nodesSelector, shallow); return nodes; } From 70ec97f7daec6d5401215cae3edac04aea88a3ba Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 16 May 2023 16:30:40 +0200 Subject: [PATCH 4/9] chore(changeset): add --- .changeset/chilled-insects-call.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilled-insects-call.md diff --git a/.changeset/chilled-insects-call.md b/.changeset/chilled-insects-call.md new file mode 100644 index 00000000..018b00c5 --- /dev/null +++ b/.changeset/chilled-insects-call.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': patch +--- + +fix useNodes and useEdges bug with infinite re-renderings From eab39cf1f36c71f9a009a6e119ca0253842fba52 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 17 May 2023 10:49:38 +0200 Subject: [PATCH 5/9] refactor(controls): disable min/max buttons when reached --- packages/controls/package.json | 7 +++++-- packages/controls/src/Controls.tsx | 11 +++++++++-- packages/controls/src/style.css | 8 ++++++++ pnpm-lock.yaml | 27 +++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/controls/package.json b/packages/controls/package.json index 271d2967..f34be8e5 100644 --- a/packages/controls/package.json +++ b/packages/controls/package.json @@ -39,7 +39,8 @@ }, "dependencies": { "@reactflow/core": "workspace:*", - "classcat": "^5.0.3" + "classcat": "^5.0.3", + "zustand": "^4.3.1" }, "peerDependencies": { "react": ">=17", @@ -55,7 +56,9 @@ }, "rollup": { "globals": { - "classcat": "cc" + "classcat": "cc", + "zustand": "Zustand", + "zustand/shallow": "zustandShallow" }, "name": "ReactFlowControls" } diff --git a/packages/controls/src/Controls.tsx b/packages/controls/src/Controls.tsx index 7c529a9e..cf8a246f 100644 --- a/packages/controls/src/Controls.tsx +++ b/packages/controls/src/Controls.tsx @@ -1,5 +1,6 @@ import { memo, useEffect, useState, type FC, type PropsWithChildren } from 'react'; import cc from 'classcat'; +import { shallow } from 'zustand/shallow'; import { useStore, useStoreApi, useReactFlow, Panel, type ReactFlowState } from '@reactflow/core'; import PlusIcon from './Icons/Plus'; @@ -11,7 +12,11 @@ import ControlButton from './ControlButton'; import type { ControlProps } from './types'; -const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable || s.nodesConnectable || s.elementsSelectable; +const selector = (s: ReactFlowState) => ({ + isInteractive: s.nodesDraggable || s.nodesConnectable || s.elementsSelectable, + minZoomReached: s.transform[2] <= s.minZoom, + maxZoomReached: s.transform[2] >= s.maxZoom, +}); const Controls: FC> = ({ style, @@ -29,7 +34,7 @@ const Controls: FC> = ({ }) => { const store = useStoreApi(); const [isVisible, setIsVisible] = useState(false); - const isInteractive = useStore(isInteractiveSelector); + const { isInteractive, minZoomReached, maxZoomReached } = useStore(selector, shallow); const { zoomIn, zoomOut, fitView } = useReactFlow(); useEffect(() => { @@ -79,6 +84,7 @@ const Controls: FC> = ({ className="react-flow__controls-zoomin" title="zoom in" aria-label="zoom in" + disabled={maxZoomReached} > @@ -87,6 +93,7 @@ const Controls: FC> = ({ className="react-flow__controls-zoomout" title="zoom out" aria-label="zoom out" + disabled={minZoomReached} > diff --git a/packages/controls/src/style.css b/packages/controls/src/style.css index 289c5060..92d55b1f 100644 --- a/packages/controls/src/style.css +++ b/packages/controls/src/style.css @@ -24,5 +24,13 @@ max-width: 12px; max-height: 12px; } + + &:disabled { + pointer-events: none; + + svg { + fill-opacity: 0.4; + } + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1b82b62..cb704934 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,9 +132,11 @@ importers: '@types/react': '>=17' classcat: ^5.0.3 typescript: ^4.9.4 + zustand: ^4.3.1 dependencies: '@reactflow/core': link:../core classcat: registry.npmjs.org/classcat/5.0.4 + zustand: registry.npmjs.org/zustand/4.3.1 devDependencies: '@reactflow/eslint-config': link:../../tooling/eslint-config '@reactflow/rollup-config': link:../../tooling/rollup-config @@ -7445,6 +7447,14 @@ packages: punycode: registry.npmjs.org/punycode/2.1.1 dev: true + registry.npmjs.org/use-sync-external-store/1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz} + name: use-sync-external-store + version: 1.2.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dev: false + registry.npmjs.org/use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz} id: registry.npmjs.org/use-sync-external-store/1.2.0 @@ -7786,6 +7796,23 @@ packages: engines: {node: '>=10'} dev: true + registry.npmjs.org/zustand/4.3.1: + resolution: {integrity: sha512-EVyo/eLlOTcJm/X5M00rwtbYFXwRVTaRteSvhtbTZUCQFJkNfIyHPiJ6Ke68MSWzcKHpPzvqNH4gC2ZS/sbNqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/zustand/-/zustand-4.3.1.tgz} + name: zustand + version: 4.3.1 + engines: {node: '>=12.7.0'} + peerDependencies: + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + immer: + optional: true + react: + optional: true + dependencies: + use-sync-external-store: registry.npmjs.org/use-sync-external-store/1.2.0 + dev: false + registry.npmjs.org/zustand/4.3.1_react@18.2.0: resolution: {integrity: sha512-EVyo/eLlOTcJm/X5M00rwtbYFXwRVTaRteSvhtbTZUCQFJkNfIyHPiJ6Ke68MSWzcKHpPzvqNH4gC2ZS/sbNqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/zustand/-/zustand-4.3.1.tgz} id: registry.npmjs.org/zustand/4.3.1 From 33915b88c2ae701847870346b381f9cfa86c6459 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 17 May 2023 10:50:44 +0200 Subject: [PATCH 6/9] chore(changeset): add --- .changeset/little-books-listen.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/little-books-listen.md diff --git a/.changeset/little-books-listen.md b/.changeset/little-books-listen.md new file mode 100644 index 00000000..e4448a4e --- /dev/null +++ b/.changeset/little-books-listen.md @@ -0,0 +1,5 @@ +--- +'@reactflow/controls': patch +--- + +disable min/max buttons when min/max is reached From 1a3f22557ce8c5b81bbf86ef4e9dda2ad610b289 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 17 May 2023 11:00:18 +0200 Subject: [PATCH 7/9] refactor(useUpdateNodeInternals): only call updateNodeDimensions once --- .../UseUpdateNodeInternals/CustomNode.tsx | 2 +- .../core/src/hooks/useUpdateNodeInternals.ts | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx b/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx index 1b25bdc1..a11c68d0 100644 --- a/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx +++ b/examples/vite-app/src/examples/UseUpdateNodeInternals/CustomNode.tsx @@ -23,7 +23,7 @@ const CustomNode: FC = ({ id }) => {