Skip to content

Commit bcaed06

Browse files
authored
Switch to Tanstack useQuery (#24)
* initial tanstack wrapping * groups to tanstack * rm debug + treedropdown * remove manual error+loading * nodeFetching on tanstack. * moved visualisers to tanstackquery * readd loading spinner
1 parent 2c3d664 commit bcaed06

11 files changed

Lines changed: 462 additions & 564 deletions

File tree

package-lock.json

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
],
3333
"dependencies": {
3434
"@spglib/moyo-wasm": "^0.7.1",
35+
"@tanstack/react-query": "^5.90.21",
3536
"@uiw/react-json-view": "^2.0.0-alpha.39",
3637
"bands-visualiser": "^0.0.2",
3738
"brillouinzone-visualizer": "^0.4.3",

src/AiidaExplorer/GroupsViewer/index.jsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useEffect, useState, useCallback, useMemo } from "react";
2+
import { useQuery } from "@tanstack/react-query";
23

34
import { fetchGroups, fetchFromQueryBuilder } from "../api";
45
import TypeCheckboxTree from "./TypeCheckboxTree";
@@ -93,7 +94,6 @@ function sortGroups(groups) {
9394
// TODO - refactor, this is very messy atm.
9495
// TODO check the search feature - maybe wire into full_types api...?
9596
export default function GroupsViewer({ restApiUrl, setRootNodeId }) {
96-
const [groups, setGroups] = useState([]);
9797
const [selectedGroups, setSelectedGroups] = useState([]);
9898
const [selectedTypes, setSelectedTypes] = useState([]);
9999
const [rawTableData, setRawTableData] = useState([]);
@@ -111,10 +111,14 @@ export default function GroupsViewer({ restApiUrl, setRootNodeId }) {
111111
? columnOrder.filter((c) => c !== "Created")
112112
: columnOrder;
113113

114-
// fetch groups once
115-
useEffect(() => {
116-
fetchGroups(restApiUrl).then(setGroups).catch(console.error);
117-
}, [restApiUrl]);
114+
const {
115+
data: groups,
116+
isLoading: groupsLoading,
117+
isError: groupsError,
118+
} = useQuery({
119+
queryKey: ["groups", restApiUrl],
120+
queryFn: () => fetchGroups(restApiUrl),
121+
});
118122

119123
// fetch nodes
120124
const fetchNodes = useCallback(
@@ -190,7 +194,7 @@ export default function GroupsViewer({ restApiUrl, setRootNodeId }) {
190194
<div className="ae:font-medium ae:my-2 ae:mt-4">
191195
Filter by Defined Groups
192196
</div>
193-
{sortGroups(groups).map((g) => (
197+
{sortGroups(groups || []).map((g) => (
194198
<label
195199
key={g.label}
196200
className="ae:flex ae:items-start ae:gap-2 ae:mb-1"

src/AiidaExplorer/VisualiserPane/Rich/BandsDataVisualiser.jsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,45 @@
11
import { BandsVisualiser } from "bands-visualiser";
22
import { useEffect, useRef, useState } from "react";
3+
import { useQuery } from "@tanstack/react-query";
34

45
import CardContainer from "../../components/CardContainer";
56
import ErrorDisplay from "../../components/Error";
67
import Spinner from "../../components/Spinner";
78

89
export default function BandsDataVisualiser({ nodeData }) {
910
const containerRef = useRef(null);
10-
const [bandsDataArray, setBandsDataArray] = useState(null);
11-
const [loading, setLoading] = useState(false);
12-
const [error, setError] = useState(null);
1311

1412
const aiidaBandsPath = nodeData.downloadByFormat?.json;
1513
const yAxisUnits = nodeData?.attributes?.units || "N/A";
1614

17-
useEffect(() => {
18-
if (!aiidaBandsPath) return;
19-
20-
const fetchData = async () => {
21-
setLoading(true);
22-
setError(null);
23-
24-
try {
25-
const res = await fetch(aiidaBandsPath);
26-
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
15+
const {
16+
data: bandsDataArray,
17+
isLoading,
18+
isError,
19+
error,
20+
refetch,
21+
} = useQuery({
22+
queryKey: ["bandsJson", aiidaBandsPath],
23+
queryFn: async () => {
24+
if (!aiidaBandsPath) {
25+
throw new Error("No bands JSON path available");
26+
}
2727

28-
const json = await res.json();
29-
setBandsDataArray([{ bandsData: json }]);
30-
} catch (err) {
31-
console.error("Failed to fetch bands JSON:", err);
32-
setError(err.message);
33-
} finally {
34-
setLoading(false);
28+
const res = await fetch(aiidaBandsPath);
29+
if (!res.ok) {
30+
throw new Error(`HTTP error: ${res.status}`);
3531
}
36-
};
3732

38-
fetchData();
39-
}, [aiidaBandsPath]); // no warnings, safe
33+
const json = await res.json();
34+
35+
// match your original structure
36+
return [{ bandsData: json }];
37+
},
38+
enabled: !!aiidaBandsPath,
39+
staleTime: 5 * 60 * 1000, // 5 minutes fresh
40+
gcTime: 5 * 60 * 1000, // cache lifetime (v5)
41+
retry: 1,
42+
});
4043

4144
useEffect(() => {
4245
if (!containerRef.current || !bandsDataArray) return;
@@ -60,13 +63,13 @@ export default function BandsDataVisualiser({ nodeData }) {
6063
childrenClassName="ae:!p-0"
6164
>
6265
<div className="ae:w-full ae:h-[450px] ae:relative ae:flex ae:items-center ae:justify-center">
63-
{loading && <Spinner />}
66+
{isLoading && <Spinner />}
6467

65-
{error && !loading && (
66-
<ErrorDisplay message={error} onRetry={() => {}} />
68+
{error && !isLoading && (
69+
<ErrorDisplay message={error} onRetry={refetch} />
6770
)}
6871

69-
{!loading && !error && bandsDataArray && (
72+
{!isLoading && !error && bandsDataArray && (
7073
<div ref={containerRef} className="ae:absolute ae:inset-0 " />
7174
)}
7275
</div>

src/AiidaExplorer/VisualiserPane/Rich/StructureDataVisualiser/index.jsx

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useMemo, useEffect, useState, useCallback } from "react";
33
import { inv, multiply } from "mathjs";
44
import StructureVisualizer from "mc-react-structure-visualizer";
55

6+
import { useQuery } from "@tanstack/react-query";
7+
68
import { StructDownloadButton } from "./StructDownloadButton";
79
import {
810
formatLattice,
@@ -24,40 +26,32 @@ import { analyzeCrystal } from "../../../spglib";
2426
// TODO - move spglib rendering and calc into its own component and make this more of a wrapper/layout component.
2527
export default function StructureDataVisualiser({ nodeData, restApiUrl }) {
2628
const aiidaCifPath = nodeData.downloadByFormat?.cif;
27-
28-
const [cifText, setCifText] = useState(null);
29-
const [loading, setLoading] = useState(true);
30-
const [error, setError] = useState(null);
3129
const [spgLib, setSpgLib] = useState(null);
3230

3331
// --- Fetch CIF text ---
34-
const fetchData = useCallback(async () => {
35-
if (!aiidaCifPath) {
36-
setError("No CIF file available");
37-
setLoading(false);
38-
return;
39-
}
40-
setLoading(true);
41-
setError(null);
42-
try {
32+
const {
33+
data: cifText,
34+
isLoading,
35+
isError,
36+
error,
37+
refetch,
38+
} = useQuery({
39+
queryKey: ["cifText", aiidaCifPath],
40+
queryFn: async () => {
41+
if (!aiidaCifPath) throw new Error("No CIF file available");
4342
const res = await fetch(aiidaCifPath);
4443
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
45-
const text = await res.text();
46-
setCifText(text);
47-
} catch (err) {
48-
setError(err.message);
49-
} finally {
50-
setLoading(false);
51-
}
52-
}, [aiidaCifPath]);
53-
54-
useEffect(() => {
55-
fetchData();
56-
}, [fetchData]);
44+
return res.text();
45+
},
46+
enabled: !!aiidaCifPath,
47+
staleTime: 1000 * 60 * 10, // 10 min cache.
48+
retry: 1,
49+
});
5750

5851
const hasDerived =
5952
nodeData.derived_properties &&
6053
Object.keys(nodeData.derived_properties).length > 0;
54+
6155
const lattice = nodeData.attributes?.cell || null;
6256
const latticeLengths = lattice ? formatLattice(nodeData) : null;
6357
const volume = hasDerived
@@ -85,7 +79,7 @@ export default function StructureDataVisualiser({ nodeData, restApiUrl }) {
8579
// --- Reverse mapping for rendering ---
8680
const reverseKindMap = useMemo(
8781
() => Object.fromEntries(Object.entries(kindMap).map(([k, v]) => [v, k])),
88-
[kindMap]
82+
[kindMap],
8983
);
9084

9185
// --- Prepare atomic sites table (Å positions) ---
@@ -100,7 +94,7 @@ export default function StructureDataVisualiser({ nodeData, restApiUrl }) {
10094
"z [Å]": z.toFixed(4),
10195
};
10296
}),
103-
[sites]
97+
[sites],
10498
);
10599

106100
// --- Density calculation ---
@@ -147,17 +141,17 @@ export default function StructureDataVisualiser({ nodeData, restApiUrl }) {
147141
}));
148142
}, [spgLib, reverseKindMap]);
149143

150-
if (loading)
144+
if (isLoading)
151145
return (
152146
<div className="ae:w-full ae:min-h-[450px] ae:flex ae:items-center ae:justify-center">
153147
<Spinner />
154148
</div>
155149
);
156150

157-
if (error)
151+
if (isError)
158152
return (
159153
<div className="ae:w-full ae:min-h-[450px] ae:flex ae:items-center ae:justify-center">
160-
<ErrorDisplay message={error} onRetry={fetchData} />
154+
<ErrorDisplay message={isError} onRetry={refetch} />
161155
</div>
162156
);
163157

0 commit comments

Comments
 (0)