Fix #769: Resolve all react-hooks/set-state-in-effect lint errors project-wide

This commit is contained in:
Sameer6305
2026-07-24 20:56:11 +05:30
parent adb46134c4
commit 343d2bc418
12 changed files with 298 additions and 90 deletions
@@ -2351,16 +2351,15 @@ export function GraphWorkspace({ externalFocusNodeId, externalFocusToken }: Grap
showPluginDock: openDockPanels.length > 0,
};
useEffect(() => {
const [prevOpenDockPanels, setPrevOpenDockPanels] = useState(openDockPanels);
if (openDockPanels !== prevOpenDockPanels) {
setPrevOpenDockPanels(openDockPanels);
if (!openDockPanels.length) {
setActiveDockPanelId(null);
return;
}
if (!activeDockPanelId || !openDockPanels.some((panel) => panel.id === activeDockPanelId)) {
if (activeDockPanelId !== null) setActiveDockPanelId(null);
} else if (!activeDockPanelId || !openDockPanels.some((panel) => panel.id === activeDockPanelId)) {
setActiveDockPanelId(openDockPanels[0].id);
}
}, [activeDockPanelId, openDockPanels]);
}
const viewModeItems = useMemo<GraphToolbarItem[]>(() => {
if (!hasGraphContent) {
@@ -369,7 +369,9 @@ export function GraphWorkspaceShell() {
}
}, []);
useEffect(() => {
const [prevFetchedAt, setPrevFetchedAt] = useState(snapshot?.fetchedAt);
if (snapshot?.fetchedAt !== prevFetchedAt) {
setPrevFetchedAt(snapshot?.fetchedAt);
if (snapshot) {
setIsGraphStageReady(false);
setActiveNodeCount(null);
@@ -383,7 +385,7 @@ export function GraphWorkspaceShell() {
stableSamples: 0,
});
}
}, [snapshot?.fetchedAt]);
}
useEffect(() => {
let cancelled = false;
@@ -51,12 +51,18 @@ export function LineageDiagram() {
document.body.removeChild(anchor);
};
useEffect(() => {
const [prevActiveId, setPrevActiveId] = useState(activeId);
if (activeId !== prevActiveId) {
setPrevActiveId(activeId);
if (!activeId) {
setNodes([]);
setEdges([]);
return;
}
}
useEffect(() => {
let ignore = false;
if (!activeId) return;
const xLanes = [
{ id: "group_agent", type: "group", position: { x: 50, y: 50 }, style: { width: 800, height: 120 } },
@@ -89,7 +95,7 @@ export function LineageDiagram() {
counters[n.parent_id] = c + 1;
return {
id: n.id,
data: { label: n.label + "\\n(" + n.prov_type + ")" },
data: { label: n.label + "\n(" + n.prov_type + ")" },
position: { x: 50 + c * 180, y: 30 },
parentId: n.parent_id,
extent: "parent",
@@ -106,13 +112,16 @@ export function LineageDiagram() {
style: { stroke: "#58a6ff" }
}));
setNodes([...xLanes, ...mappedNodes]);
setEdges(mappedEdges);
if (!ignore) {
setNodes([...xLanes, ...mappedNodes]);
setEdges(mappedEdges);
}
} catch (err) {
console.error(err);
}
};
fetchLineage();
void fetchLineage();
return () => { ignore = true; };
}, [activeId]);
return (
@@ -116,8 +116,51 @@ export function KGOverviewTab() {
}, []);
useEffect(() => {
void fetchOverview();
}, [fetchOverview]);
let ignore = false;
async function fetchInitial() {
try {
const [statsRes, nodesRes] = await Promise.all([
fetch("/api/graph/stats"),
fetch("/api/graph/nodes?limit=500"),
]);
if (!ignore) {
if (statsRes.ok) {
const statsData: KGStats = await statsRes.json();
setStats(statsData);
}
if (nodesRes.ok) {
const nodesData: NodeListResponse = await nodesRes.json();
const nodes = nodesData.nodes ?? [];
setNodeTypeMap(buildTypeMap(nodes, "type"));
const edgesRes = await fetch("/api/graph/edges?limit=2000");
if (edgesRes.ok) {
const edgesData = await edgesRes.json();
const edges: { source: string; target: string }[] = edgesData.edges ?? [];
const degreeMap: Record<string, number> = {};
for (const edge of edges) {
degreeMap[edge.source] = (degreeMap[edge.source] ?? 0) + 1;
degreeMap[edge.target] = (degreeMap[edge.target] ?? 0) + 1;
}
const sorted = nodes
.map((n) => ({ node: n, neighborCount: degreeMap[n.id] ?? 0 }))
.sort((a, b) => b.neighborCount - a.neighborCount)
.slice(0, 10);
setTopNodes(sorted);
}
}
}
} catch {
if (!ignore) setError("Failed to load graph overview. Ensure the server is running.");
} finally {
if (!ignore) setLoading(false);
}
}
void fetchInitial();
return () => { ignore = true; };
}, []);
const nodeTypeEntries = Object.entries(nodeTypeMap).sort((a, b) => b[1] - a[1]);
const edgeTypeEntries = stats?.edge_types
@@ -66,8 +66,27 @@ export function AlignmentsTab() {
}, []);
useEffect(() => {
void reload();
}, [reload]);
let ignore = false;
async function fetchInitial() {
const [registryResult, alignmentResult] = await Promise.allSettled([
loadOntologyRegistry(),
loadAlignments(),
]);
if (!ignore) {
if (registryResult.status === "fulfilled") {
setRegistry(registryResult.value);
setSourceOntology((current) => current || registryResult.value[0]?.uri || "");
setTargetOntology((current) => current || registryResult.value[1]?.uri || registryResult.value[0]?.uri || "");
}
if (alignmentResult.status === "fulfilled") {
setAlignments(alignmentResult.value);
}
}
}
void fetchInitial();
return () => { ignore = true; };
}, []);
const relationCounts = useMemo(() => {
const counts = new Map<string, number>();
@@ -29,23 +29,31 @@ export function HealthTab({ onFixInEditor }: HealthTabProps) {
};
}, []);
const loadHealth = useCallback(async (uri: string) => {
if (!uri) return;
setLoading(true);
setError("");
try {
setHealth(await loadOntologyHealth(uri));
} catch {
// Backend unavailable — show "select an ontology" placeholder, not an error
setHealth(null);
} finally {
setLoading(false);
const [prevUri, setPrevUri] = useState(selectedUri);
if (selectedUri !== prevUri) {
setPrevUri(selectedUri);
if (selectedUri) {
setLoading(true);
setError("");
}
}, []);
}
useEffect(() => {
void loadHealth(selectedUri);
}, [selectedUri, loadHealth]);
let ignore = false;
async function fetchHealth() {
if (!selectedUri) return;
try {
const data = await loadOntologyHealth(selectedUri);
if (!ignore) setHealth(data);
} catch {
if (!ignore) setHealth(null);
} finally {
if (!ignore) setLoading(false);
}
}
void fetchHealth();
return () => { ignore = true; };
}, [selectedUri]);
const exportReport = useCallback(() => {
if (!health) return;
@@ -248,6 +248,15 @@ export function OntologyManager() {
const [rightPanel, setRightPanel] = useState<RightPanel>("none");
const [actionMsg, setActionMsg] = useState<{ type: "ok" | "err"; text: string } | null>(null);
const [prevSearchQ, setPrevSearchQ] = useState(searchQ);
const [prevStatusFilter, setPrevStatusFilter] = useState(statusFilter);
if (searchQ !== prevSearchQ || statusFilter !== prevStatusFilter) {
setPrevSearchQ(searchQ);
setPrevStatusFilter(statusFilter);
setLoading(true);
setActionMsg(null);
}
const fetchRegistry = useCallback(async () => {
setLoading(true);
setActionMsg(null);
@@ -268,8 +277,27 @@ export function OntologyManager() {
}, [searchQ, statusFilter]);
useEffect(() => {
fetchRegistry();
}, [fetchRegistry]);
let ignore = false;
async function fetchInitial() {
try {
const params = new URLSearchParams();
if (searchQ) params.set("q", searchQ);
const res = await fetch(`/api/ontology/registry?${params}`);
if (res.ok) {
const data = await res.json();
if (!ignore) setEntries(data);
} else {
if (!ignore) setEntries([]);
}
} catch {
if (!ignore) setEntries([]);
} finally {
if (!ignore) setLoading(false);
}
}
void fetchInitial();
return () => { ignore = true; };
}, [searchQ, statusFilter]);
const flashMsg = (type: "ok" | "err", text: string) => {
setActionMsg({ type, text });
@@ -178,17 +178,31 @@ function DetailPanel({
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const [prevUri, setPrevUri] = useState(uri);
if (uri !== prevUri) {
setPrevUri(uri);
setLoading(true);
setError("");
setDetail(null);
}
useEffect(() => {
let ignore = false;
fetch(`/api/ontology/entity/${encodeURIComponent(uri)}`)
.then((r) => {
if (!r.ok) throw new Error("Not found");
return r.json();
})
.then(setDetail)
.catch((e) => setError(e.message))
.finally(() => setLoading(false));
.then((data) => {
if (!ignore) setDetail(data);
})
.catch((e) => {
if (!ignore) setError(e.message);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => { ignore = true; };
}, [uri]);
return (
@@ -40,19 +40,6 @@ export function ProposalReview({ proposalId }: { proposalId: string }) {
const [selectedElement, setSelectedElement] = useState<string | null>(null);
const [commentText, setCommentText] = useState("");
const loadProposal = useCallback(async () => {
try {
const response = await fetch(`/api/ontology/proposals/${proposalId}`);
if (response.ok) {
const data = await response.json();
setProposal(data);
generateDiff(data);
}
} catch (error) {
console.error("Failed to load proposal:", error);
}
}, [proposalId]);
const generateDiff = useCallback((prop: Proposal) => {
const changes: DiffChange[] = [];
@@ -75,9 +62,38 @@ export function ProposalReview({ proposalId }: { proposalId: string }) {
setDiff(changes);
}, []);
const loadProposal = useCallback(async () => {
try {
const response = await fetch(`/api/ontology/proposals/${proposalId}`);
if (response.ok) {
const data = await response.json();
setProposal(data);
generateDiff(data);
}
} catch (error) {
console.error("Failed to load proposal:", error);
}
}, [proposalId, generateDiff]);
useEffect(() => {
loadProposal();
}, [loadProposal]);
let ignore = false;
async function fetchInitial() {
try {
const response = await fetch(`/api/ontology/proposals/${proposalId}`);
if (response.ok) {
const data = await response.json();
if (!ignore) {
setProposal(data);
generateDiff(data);
}
}
} catch (error) {
console.error("Failed to load proposal:", error);
}
}
void fetchInitial();
return () => { ignore = true; };
}, [proposalId, generateDiff]);
const addComment = useCallback(async () => {
if (!selectedElement || !commentText || !proposal) return;
@@ -77,17 +77,33 @@ function ConceptDetailPanel({
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const [prevUri, setPrevUri] = useState(uri);
if (uri !== prevUri) {
setPrevUri(uri);
setLoading(true);
setError("");
setDetail(null);
}
useEffect(() => {
let ignore = false;
fetch(`/api/ontology/skos/concept/${encodeURIComponent(uri)}`)
.then((r) => {
if (!r.ok) throw new Error("Concept not found");
return r.json();
})
.then(setDetail)
.catch((e) => setError(e.message))
.finally(() => setLoading(false));
.then((data) => {
if (!ignore) setDetail(data);
})
.catch((e) => {
if (!ignore) setError(e.message);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true;
};
}, [uri]);
const renderUriList = (label: string, uris: string[]) => {
@@ -322,16 +338,36 @@ function SchemePanel({
}) {
const [expanded, setExpanded] = useState(true);
const [hierarchy, setHierarchy] = useState<ConceptNode[]>([]);
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(expanded);
const [prevExpanded, setPrevExpanded] = useState(expanded);
const [prevSchemeUri, setPrevSchemeUri] = useState(scheme.uri);
if (expanded !== prevExpanded || scheme.uri !== prevSchemeUri) {
setPrevExpanded(expanded);
setPrevSchemeUri(scheme.uri);
if (expanded) {
setLoading(true);
setHierarchy([]);
}
}
useEffect(() => {
let ignore = false;
if (!expanded) return;
setLoading(true);
fetch(`/api/vocabulary/hierarchy?scheme=${encodeURIComponent(scheme.uri)}`)
.then((r) => (r.ok ? r.json() : []))
.then(setHierarchy)
.catch(() => setHierarchy([]))
.finally(() => setLoading(false));
.then((data) => {
if (!ignore) setHierarchy(data);
})
.catch(() => {
if (!ignore) setHierarchy([]);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true;
};
}, [scheme.uri, expanded]);
const totalConcepts = countConcepts(hierarchy);
@@ -410,7 +446,6 @@ export function SKOSVocabularyManager({ schemeUri }: Props) {
const [selectedUri, setSelectedUri] = useState<string | null>(null);
useEffect(() => {
setLoading(true);
fetch("/api/ontology/skos/schemes")
.then((r) => (r.ok ? r.json() : []))
.then(setSchemes)
@@ -39,32 +39,42 @@ export function ShaclStudio({ onJumpToNode }: ShaclStudioProps) {
};
}, []);
const loadShapes = useCallback(async (uri: string) => {
if (!uri) return;
setLoading(true);
setError("");
try {
const data = await loadShaclShapes(uri);
setShapes(data.shapes);
const turtle = data.shacl_turtle;
setFullShacl(turtle);
setShacl((current) => current || turtle);
setSelectedShapeId(null);
setValidation(null);
} catch {
// Shapes not yet generated or backend unavailable — show empty shape list
setShapes([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
const [prevUri, setPrevUri] = useState(selectedUri);
if (selectedUri !== prevUri) {
setPrevUri(selectedUri);
setShacl("");
setFullShacl("");
setSelectedShapeId(null);
void loadShapes(selectedUri);
}, [selectedUri, loadShapes]);
setValidation(null);
setLoading(true);
setError("");
}
useEffect(() => {
let ignore = false;
async function fetchShapes() {
if (!selectedUri) return;
try {
const data = await loadShaclShapes(selectedUri);
if (!ignore) {
setShapes(data.shapes);
const turtle = data.shacl_turtle;
setFullShacl(turtle);
setShacl((current) => current || turtle);
setSelectedShapeId(null);
setValidation(null);
}
} catch {
if (!ignore) setShapes([]);
} finally {
if (!ignore) setLoading(false);
}
}
void fetchShapes();
return () => {
ignore = true;
};
}, [selectedUri]);
const handleGenerate = useCallback(async () => {
if (!selectedUri) return;
@@ -74,9 +74,34 @@ export function VersionsTab() {
}, []);
useEffect(() => {
loadVersions();
loadProposals();
}, [loadVersions, loadProposals]);
let ignore = false;
async function fetchInitial() {
try {
const [propRes] = await Promise.all([
fetch("/api/ontology/proposals"),
]);
if (propRes.ok) {
const propData = await propRes.json();
if (!ignore) setProposals(propData);
}
} catch (e) {
console.error("Failed to load proposals", e);
}
if (!ontologyUri) return;
try {
const verRes = await fetch(`/api/ontology/versions/${encodeURIComponent(ontologyUri)}`);
if (verRes.ok) {
const verData = await verRes.json();
if (!ignore) setVersions(verData);
}
} catch (e) {
console.error("Failed to load versions", e);
}
}
void fetchInitial();
return () => { ignore = true; };
}, [ontologyUri]);
const approveProposal = useCallback(async (proposalId: string) => {
try {