diff --git a/package-lock.json b/package-lock.json
index f9c918944..9fc433029 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "treetracker-admin-client",
- "version": "1.107.2",
+ "version": "1.108.0-hotfix-v1-107.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "treetracker-admin-client",
- "version": "1.107.2",
+ "version": "1.108.0-hotfix-v1-107.1",
"dependencies": {
"@date-io/date-fns": "^1.3.13",
"@material-ui/core": "^4.9.10",
@@ -16,6 +16,7 @@
"@material-ui/styles": "^4.3.0",
"@material-ui/system": "^4.3.2",
"@rematch/core": "*",
+ "@tanstack/react-query": "^5.85.5",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
@@ -43,11 +44,11 @@
"npm": "*",
"os": "npm:os-browserify",
"prop-types": "*",
- "react": "^18",
+ "react": "^18.2.0",
"react-autosuggest": "^10.0.2",
"react-chartjs-2": "^4.0.1",
"react-csv": "^2.0.3",
- "react-dom": "^18",
+ "react-dom": "^18.2.0",
"react-fast-compare": "^3.2.0",
"react-infinite": "*",
"react-redux": "*",
@@ -6312,6 +6313,30 @@
"node": ">=0.10.0"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
+ "integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
+ "integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
+ "dependencies": {
+ "@tanstack/query-core": "5.85.5"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.14.0.tgz",
@@ -22420,6 +22445,8 @@
},
"node_modules/npm/node_modules/debug/node_modules/ms": {
"version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"inBundle": true,
"license": "MIT"
},
@@ -23249,6 +23276,8 @@
},
"node_modules/npm/node_modules/node-gyp/node_modules/glob": {
"version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -23710,6 +23739,8 @@
},
"node_modules/npm/node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -23787,6 +23818,8 @@
},
"node_modules/npm/node_modules/semver/node_modules/lru-cache": {
"version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -39557,6 +39590,19 @@
}
}
},
+ "@tanstack/query-core": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
+ "integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w=="
+ },
+ "@tanstack/react-query": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
+ "integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
+ "requires": {
+ "@tanstack/query-core": "5.85.5"
+ }
+ },
"@testing-library/dom": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.14.0.tgz",
@@ -52010,6 +52056,8 @@
"dependencies": {
"ms": {
"version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"bundled": true
}
}
@@ -52607,6 +52655,8 @@
},
"glob": {
"version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"bundled": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -52929,6 +52979,8 @@
},
"glob": {
"version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"bundled": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -52974,6 +53026,8 @@
"dependencies": {
"lru-cache": {
"version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"bundled": true,
"requires": {
"yallist": "^4.0.0"
diff --git a/package.json b/package.json
index d4eb44b72..a96852ec3 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"@material-ui/styles": "^4.3.0",
"@material-ui/system": "^4.3.2",
"@rematch/core": "*",
+ "@tanstack/react-query": "^5.85.5",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
diff --git a/src/components/Home/Home.js b/src/components/Home/Home.js
index 1a469f3ef..f44994840 100644
--- a/src/components/Home/Home.js
+++ b/src/components/Home/Home.js
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useContext } from 'react';
-
+import { useQueryClient } from '@tanstack/react-query';
+import api from '../../api/treeTrackerApi';
import FilterListIcon from '@material-ui/icons/FilterList';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
@@ -71,6 +72,15 @@ function Home(props) {
}
loadUpdateTime();
}, []);
+ const queryClient = useQueryClient();
+ useEffect(() => {
+ queryClient.prefetchQuery({
+ queryKey: ['species'],
+ queryFn: () => api.getSpecies(),
+ staleTime: 1000 * 60 * 5, // cache for 5 mins
+ refetchOnWindowFocus: false,
+ });
+ }, [queryClient]);
const timeRange = [
{ range: 30, text: 'Last Month' },
diff --git a/src/context/SpeciesContext.js b/src/context/SpeciesContext.js
index 3f9af0600..acf3c650c 100644
--- a/src/context/SpeciesContext.js
+++ b/src/context/SpeciesContext.js
@@ -1,6 +1,7 @@
-import React, { useState, useEffect, createContext } from 'react';
+import React, { useState, createContext } from 'react';
import api from '../api/treeTrackerApi';
import * as loglevel from 'loglevel';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
const log = loglevel.getLogger('../context/SpeciesContext');
@@ -9,7 +10,6 @@ export const SpeciesContext = createContext({
speciesList: [],
speciesInput: '',
setSpeciesInput: () => {},
- loadSpeciesList: () => {},
onChange: () => {},
isNewSpecies: () => {},
createSpecies: () => {},
@@ -19,44 +19,31 @@ export const SpeciesContext = createContext({
combineSpecies: () => {},
});
-export function SpeciesProvider(props) {
- const [speciesList, setSpeciesList] = useState([]);
- const [speciesInput, setSpeciesInput] = useState(''); // only used by Species dropdown and Verify
- const [isLoading, setIsLoading] = useState(false);
+export function SpeciesProvider({ children }) {
+ const [speciesInput, setSpeciesInput] = useState('');
+ const queryClient = useQueryClient();
- useEffect(() => {
- const abortController = new AbortController();
- loadSpeciesList({ signal: abortController.signal });
- return () => abortController.abort();
- }, []);
-
- // EVENT HANDLERS
-
- const loadSpeciesList = async (abortController) => {
- setIsLoading(true);
- const species = await api.getSpecies(abortController);
- setSpeciesList(species);
- setIsLoading(false);
- };
+ const { data: speciesList = [], isLoading, refetch } = useQuery({
+ queryKey: ['species'],
+ queryFn: () => api.getSpecies(),
+ staleTime: 1000 * 60 * 5,
+ refetchOnWindowFocus: false,
+ enabled: false, // don’t auto-fetch, rely on Home preload
+ });
// only used by Species dropdown
- const onChange = async (text) => {
+ const onChange = (text) => {
console.log('on change:"', text, '"');
setSpeciesInput(text);
+
+ // ✅ Fallback: if species list is empty (not prefetched yet), trigger fetch
+ if (!speciesList.length) {
+ refetch();
+ }
};
- // only used by Verify
const isNewSpecies = () => {
- //check input is valid and doesn't already exist
- if (!speciesInput) {
- log.debug('empty species, false');
- return false;
- }
- log.debug(
- 'to find species %s in list:%d',
- speciesInput,
- speciesList.length
- );
+ if (!speciesInput) return false;
return speciesList.every(
(c) => c.name.toLowerCase() !== speciesInput.toLowerCase()
);
@@ -64,45 +51,35 @@ export function SpeciesProvider(props) {
const createSpecies = async (payload) => {
const species = await api.createSpecies(
- payload || {
- name: speciesInput,
- desc: '',
- }
+ payload || { name: speciesInput, desc: '' }
);
console.debug('created new species:', species);
- setSpeciesList([species, ...speciesList]);
+ // update cache
+ queryClient.setQueryData(['species'], (old = []) => [species, ...old]);
};
- //to get the species id according the current speciesInput
const getSpeciesId = () => {
if (speciesInput) {
- return speciesList.reduce((a, c) => {
- if (a) {
- return a;
- } else if (c.name === speciesInput) {
- return c.id;
- } else {
- return a;
- }
- }, undefined);
+ return speciesList.find((c) => c.name === speciesInput)?.id;
}
};
- const editSpecies = async (payload) => {
- const { id, name, desc } = payload;
+ const editSpecies = async ({ id, name, desc }) => {
const editedSpecies = await api.editSpecies(id, name, desc);
console.debug('edit old species:', editedSpecies);
+ // refetch species list after editing
+ queryClient.invalidateQueries(['species']);
};
- const deleteSpecies = async (payload) => {
- const { id } = payload;
- const deletedSpecies = await api.deleteSpecies(id);
- console.debug('delete outdated species:', id, deletedSpecies);
+ const deleteSpecies = async ({ id }) => {
+ await api.deleteSpecies(id);
+ console.debug('delete outdated species:', id);
+ queryClient.invalidateQueries(['species']);
};
- const combineSpecies = async (payload) => {
- const { combine, name, desc } = payload;
+ const combineSpecies = async ({ combine, name, desc }) => {
await api.combineSpecies(combine, name, desc);
+ queryClient.invalidateQueries(['species']);
};
const value = {
@@ -110,7 +87,6 @@ export function SpeciesProvider(props) {
speciesList,
speciesInput,
setSpeciesInput,
- loadSpeciesList,
onChange,
isNewSpecies,
createSpecies,
@@ -119,9 +95,8 @@ export function SpeciesProvider(props) {
deleteSpecies,
combineSpecies,
};
+
return (
-
- {props.children}
-
+ {children}
);
}
diff --git a/src/index.js b/src/index.js
index 014093385..a27f259c3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,4 +5,12 @@ import App from './App';
import 'typeface-roboto';
import './index.css';
-ReactDOM.createRoot(document.getElementById('root')).render();
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+const queryClient = new QueryClient();
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+);
diff --git a/yarn.lock b/yarn.lock
index 807ae11f4..75bac1fca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3232,6 +3232,18 @@
"@svgr/plugin-svgo" "^5.5.0"
"loader-utils" "^2.0.0"
+"@tanstack/query-core@5.85.5":
+ "integrity" "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w=="
+ "resolved" "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz"
+ "version" "5.85.5"
+
+"@tanstack/react-query@^5.85.5":
+ "integrity" "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A=="
+ "resolved" "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz"
+ "version" "5.85.5"
+ dependencies:
+ "@tanstack/query-core" "5.85.5"
+
"@testing-library/dom@^8.0.0":
"integrity" "sha512-m8FOdUo77iMTwVRCyzWcqxlEIk+GnopbrRI15a0EaLbpZSCinIVI4kSQzWhkShK83GogvEFJSsHF3Ws0z1vrqA=="
"resolved" "https://registry.npmjs.org/@testing-library/dom/-/dom-8.14.0.tgz"
@@ -15189,7 +15201,7 @@
"node-dir" "^0.1.10"
"strip-indent" "^3.0.0"
-"react-dom@^18":
+"react-dom@^18.2.0":
"integrity" "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
"version" "18.2.0"
@@ -15443,7 +15455,7 @@
"loose-envify" "^1.4.0"
"prop-types" "^15.6.2"
-"react@^18":
+"react@^18.2.0":
"integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="
"resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
"version" "18.2.0"