Skip to content

Rework graph selection logic #1515

@kmcginnes

Description

@kmcginnes

Problem

The current selection architecture has a bidirectional sync problem between Cytoscape and React:

  1. React drives Cytoscape selection via props — selectedNodesIds and selectedEdgesIds arrays are passed into , and useEntitySelection runs a useEffect to imperatively select/unselect Cytoscape elements to match those props.

  2. Cytoscape drives React selection via callbacks — When the user clicks in Cytoscape, select/unselect events fire, which call onSelectedElementIdsChange, which calls replaceGraphSelection, which updates Jotai atoms (nodesSelectedIdsAtom, edgesSelectedIdsAtom), which re-renders, which passes new props back into , which triggers useEntitySelection again.

This creates a circular data flow: Cytoscape → callbacks → Jotai atoms → props → useEntitySelection → Cytoscape. The debounced event handlers and change-detection refs in useManageElementsSelection are band-aids to prevent infinite loops.

Additionally, onSelectedElementIdsChange provides separate nodeIds/edgeIds/groupIds sets, so selection order is lost — there's no way to know which element was selected first.

Proposed Changes

1. Make Cytoscape the single source of truth for selection state

  • Remove selectedNodesIds, selectedEdgesIds, and selectedGroupsIds from GraphProps / Selection type.
  • Remove useEntitySelection (the hook that pushes React state into Cytoscape).
  • Remove the Jotai atoms nodesSelectedIdsAtom and edgesSelectedIdsAtom as the authoritative selection state.

2. Read selection state from Cytoscape using useSyncExternalStore

  • Subscribe to Cytoscape's select/unselect events as the external store.
  • Expose a single ordered list of selected element IDs (preserving selection order) via a hook, likely integrated into useGraphSelection.
  • This replaces the current pattern of debounced event handlers writing back to Jotai atoms.

3. Set selection imperatively through graphRef, not through state

  • Add a setSelection method to GraphRef (e.g., graphRef.current.setSelection({ vertices, edges })).
  • This directly calls cy.getElementById(id).select() / .unselect() — no round-trip through React state.
  • Expose this through GraphProvider / useGraphRef so any React code can imperatively set selection.

4. Keep useGraphSelection as the public API

  • useGraphSelection continues to be the hook consumers use (GraphViewer, ContextMenu, NodesTabular, EdgesTabular, etc.).
  • Internally, it switches from reading Jotai atoms to reading the useSyncExternalStore-backed Cytoscape selection.
  • replaceGraphSelection switches from writing Jotai atoms to calling the imperative graphRef.current.setSelection(...).
  • Consumer code in ~6 files stays largely unchanged.

5. Remove the onSelectedElementIdsChange callback pattern

  • The separate onSelectedNodesIdsChange, onSelectedEdgesIdsChange, onSelectedGroupsIdsChange, and onSelectedElementIdsChange callbacks on GraphProps become unnecessary since React reads selection directly from Cytoscape.
  • useManageElementsSelection can be significantly simplified or removed — it no longer needs to sync in either direction.

Files Primarily Affected

File Change
components/Graph/Graph.model.ts Remove Selection type, SelectedElements
components/Graph/Graph.tsx / GraphProps Remove selection props and callbacks
components/Graph/hooks/useManageElementsSelection.ts Remove or heavily simplify
components/Graph/GraphContext.tsx Expand GraphRef with setSelection
modules/GraphViewer/useGraphSelection.ts Rewrite internals to use useSyncExternalStore + graphRef
modules/GraphViewer/GraphViewer.tsx Remove selection prop wiring to
core/StateProvider/nodes.ts / edges.ts Remove nodesSelectedIdsAtom / edgesSelectedIdsAtom
Consumers of the atoms directly (displayVertex.ts, displayEdge.ts, EntitiesRefreshButton.tsx, NodeExpand.tsx, useRemoveFromGraph.ts, useResetState.ts) Switch to useGraphSelection or read from the new source

Metadata

Metadata

Assignees

Labels

explorationIssues related to graph exploration, node & edge details, neighbor expansion, etcfundamentalreliabilityIssues relating to improvements in reliability

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions