Fix store subscription storm with shallow equality

Critical Performance Issue:
- Every CustomNode and CustomEdge subscribed to entire store arrays
- 100 nodes × 2 subscriptions + 200 edges × 3 subscriptions = 800 listeners
- ANY change to nodeTypes/labels/edgeTypes triggered ALL components to re-render
- Example: Changing one node type color → 300 components re-render

Solution:
- Add shallow equality checking to all store subscriptions
- Components now only re-render when array CONTENTS change
- Prevents cascade re-renders from reference changes

Files Modified:
- CustomNode.tsx: nodeTypes, labels with shallow
- CustomEdge.tsx: edgeTypes, labels, nodeTypes with shallow

Expected Impact:
- Eliminates unnecessary re-renders during viewport changes
- Should dramatically improve responsiveness during pan/zoom
- Reduces re-render churn when editing types/labels

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik 2026-02-05 15:03:49 +01:00
parent de87be5f66
commit 8feccb6a48
2 changed files with 7 additions and 5 deletions

View file

@ -6,6 +6,7 @@ import {
useInternalNode,
} from '@xyflow/react';
import { useGraphStore } from '../../stores/graphStore';
import { shallow } from 'zustand/shallow';
import type { Relation } from '../../types';
import LabelBadge from '../Common/LabelBadge';
import { getFloatingEdgeParams } from '../../utils/edgeUtils';
@ -35,9 +36,9 @@ const CustomEdge = ({
data,
selected,
}: EdgeProps<Relation>) => {
const edgeTypes = useGraphStore((state) => state.edgeTypes);
const labels = useGraphStore((state) => state.labels);
const nodeTypes = useGraphStore((state) => state.nodeTypes);
const edgeTypes = useGraphStore((state) => state.edgeTypes, shallow);
const labels = useGraphStore((state) => state.labels, shallow);
const nodeTypes = useGraphStore((state) => state.nodeTypes, shallow);
// Get active filters based on mode (editing vs presentation)
const filters = useActiveFilters();

View file

@ -1,6 +1,7 @@
import { memo, useMemo } from "react";
import { Handle, Position, NodeProps } from "@xyflow/react";
import { useGraphStore } from "../../stores/graphStore";
import { shallow } from "zustand/shallow";
import {
getContrastColor,
adjustColorBrightness,
@ -26,8 +27,8 @@ import {
* Usage: Automatically rendered by React Flow for nodes with type='custom'
*/
const CustomNode = ({ data, selected }: NodeProps<Actor>) => {
const nodeTypes = useGraphStore((state) => state.nodeTypes);
const labels = useGraphStore((state) => state.labels);
const nodeTypes = useGraphStore((state) => state.nodeTypes, shallow);
const labels = useGraphStore((state) => state.labels, shallow);
// Get active filters based on mode (editing vs presentation)
const filters = useActiveFilters();