fix(topology): stop infinite render loop crashing the Topology page (#37) (#39)

The Topology page crashed on load with React error #185 ("Maximum update
depth exceeded"), thrown from inside React Flow's setNodes.

Root cause: handleDeleteManual depended on the whole TanStack Query
useMutation result object, which is recreated on every render. That made
handleDeleteManual unstable, which made the `graph` useMemo recompute every
render, which made the node/edge sync effect fire every render — calling
setNodes/setEdges in a loop until React aborted.

Fix: depend on the referentially-stable `.mutate` function instead of the
whole mutation object, so handleDeleteManual stays stable and `graph` only
rebuilds when the underlying data or connectMode actually change.

Verified: tsc --noEmit clean, eslint clean, vite build succeeds, and the
Topology page renders without #185 in a local Docker build.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2GT-Rich
2026-06-01 16:08:57 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 914ded24b0
commit 6dd855476e
+9 -2
View File
@@ -516,10 +516,17 @@ export default function TopologyPage() {
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['topology'] }), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['topology'] }),
}); });
// Depend on the stable `.mutate` function rather than the whole mutation object.
// TanStack Query recreates the mutation result object on every render but keeps
// `.mutate` referentially stable, so this keeps handleDeleteManual stable too.
// Depending on the whole object would make handleDeleteManual — and therefore the
// `graph` memo and the node/edge sync effect below — re-run on every render,
// causing an infinite setNodes/setEdges loop (React error #185).
const deleteManualLinkMutate = deleteManualLinkMutation.mutate;
const handleDeleteManual = useCallback((edgeId: string) => { const handleDeleteManual = useCallback((edgeId: string) => {
const id = parseInt(edgeId); const id = parseInt(edgeId);
if (!isNaN(id)) deleteManualLinkMutation.mutate(id); if (!isNaN(id)) deleteManualLinkMutate(id);
}, [deleteManualLinkMutation]); }, [deleteManualLinkMutate]);
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]); const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]); const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);