fix(web): fix stale closure in sigma event handlers breaking node selection

The sigma 'clickNode', 'clickStage', 'enterNode', and 'leaveNode' handlers
are registered in a one-time useEffect (empty dep array). They captured
options.onNodeClick via closure, so they always called the initial version
of handleNodeClick — the one created before the graph loaded where
`if (!graph) return` exits immediately.

Consequence: clicking a node in the canvas never updated the app-level
selectedNode state. This broke:
  - The Focus Depth filter (warning "Select a node to apply depth filter"
    persisted even after a canvas click)
  - The depth hop filter not applying (selectedNode was always null)
  - The code panel not opening on canvas node click

Fix: store the three callback props in refs (onNodeClickRef, onNodeHoverRef,
onStageClickRef) and update them synchronously on every render. The sigma
event handlers now read from the refs, so they always invoke the latest
version of the callbacks without needing to re-register.

Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
This commit is contained in:
Hugo Gu 2026-05-25 22:50:43 +08:00
parent 8e445e05c8
commit e2f0def372

View file

@ -238,6 +238,15 @@ export const useSigma = (options: UseSigmaOptions = {}): UseSigmaReturn => {
const blastRadiusRef = useRef<Set<string>>(new Set());
const animatedNodesRef = useRef<Map<string, NodeAnimation>>(new Map());
const visibleEdgeTypesRef = useRef<EdgeType[] | null>(null);
// Keep callback refs fresh so the one-time sigma event handlers always
// call the latest version (avoids stale-closure bugs when graph loads).
const onNodeClickRef = useRef(options.onNodeClick);
const onNodeHoverRef = useRef(options.onNodeHover);
const onStageClickRef = useRef(options.onStageClick);
onNodeClickRef.current = options.onNodeClick;
onNodeHoverRef.current = options.onNodeHover;
onStageClickRef.current = options.onStageClick;
const layoutTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const effectsAnimationFrameRef = useRef<number | null>(null);
const treeLayoutFrameRef = useRef<number | null>(null);
@ -665,23 +674,23 @@ export const useSigma = (options: UseSigmaOptions = {}): UseSigmaReturn => {
sigma.on('clickNode', ({ node }) => {
setSelectedNode(node);
options.onNodeClick?.(node);
onNodeClickRef.current?.(node);
});
sigma.on('clickStage', () => {
setSelectedNode(null);
options.onStageClick?.();
onStageClickRef.current?.();
});
sigma.on('enterNode', ({ node }) => {
options.onNodeHover?.(node);
onNodeHoverRef.current?.(node);
if (containerRef.current) {
containerRef.current.style.cursor = 'pointer';
}
});
sigma.on('leaveNode', () => {
options.onNodeHover?.(null);
onNodeHoverRef.current?.(null);
if (containerRef.current) {
containerRef.current.style.cursor = 'grab';
}