From 6dd855476e180c366223fa8262d7ab573fe657a9 Mon Sep 17 00:00:00 2001 From: 2GT-Rich <107219997+2GT-Rich@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:08:57 -0700 Subject: [PATCH] fix(topology): stop infinite render loop crashing the Topology page (#37) (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/pages/TopologyPage.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/TopologyPage.tsx b/frontend/src/pages/TopologyPage.tsx index 804c5d8..879519e 100644 --- a/frontend/src/pages/TopologyPage.tsx +++ b/frontend/src/pages/TopologyPage.tsx @@ -516,10 +516,17 @@ export default function TopologyPage() { 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 id = parseInt(edgeId); - if (!isNaN(id)) deleteManualLinkMutation.mutate(id); - }, [deleteManualLinkMutation]); + if (!isNaN(id)) deleteManualLinkMutate(id); + }, [deleteManualLinkMutate]); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]);