Skip to content

Commit

Permalink
Replace use-http with react-query
Browse files Browse the repository at this point in the history
  • Loading branch information
bnord01 committed Jan 17, 2024
1 parent 74277ba commit 5a6306b
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 558 deletions.
683 changes: 200 additions & 483 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@fluentui/font-icons-mdl2": "^8.5.23",
"@fluentui/react": "^8.110.12",
"@fluentui/react-hooks": "^8.6.29",
"@tanstack/react-query": "^5.17.15",
"@types/leaflet": "^1.9.3",
"@types/node": "^20.4.9",
"@types/react": "^18",
Expand All @@ -16,8 +17,7 @@
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-leaflet": "^4.2.1",
"use-http": "^1.0.28"
"react-leaflet": "^4.2.1"
},
"scripts": {
"prebuild": "rm -rf dist",
Expand Down
40 changes: 26 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useBoolean } from "@fluentui/react-hooks";
import MoistureMarkers from "./components/MoistureMarkers";
import { Info, Imprint } from "./components/Markdown";
import { createContext, useEffect, useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export enum HistoryWindow {
hourly = "1h",
Expand All @@ -19,6 +20,15 @@ export const HistoryWindowContext = createContext<{
setHistoryWindow: React.Dispatch<React.SetStateAction<HistoryWindow>>;
}>({ historyWindow: HistoryWindow.daily, setHistoryWindow: () => {} });

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 60 * 1000,
gcTime: 60 * 60 * 1000,
},
},
});

function App() {
const position: [number, number] = [52.01, 8.542732];
const zoom = 13;
Expand All @@ -37,20 +47,22 @@ function App() {
});

return (
<HistoryWindowContext.Provider value={{ historyWindow, setHistoryWindow }}>
<div className="App">
<SidePanel isOpen={infoOpen} dismissPanel={() => dismissInfo()} children={<Info />} />
<SidePanel isOpen={imprintOpen} dismissPanel={() => dismissImprint()} children={<Imprint />} />
<MapContainer zoomControl={false} center={position} zoom={zoom} style={{ height: height }}>
<MoistureMarkers />
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<AppCommandBar openImprint={openImprint} openInfo={openInfo} />
</MapContainer>
</div>
</HistoryWindowContext.Provider>
<QueryClientProvider client={queryClient}>
<HistoryWindowContext.Provider value={{ historyWindow, setHistoryWindow }}>
<div className="App">
<SidePanel isOpen={infoOpen} dismissPanel={() => dismissInfo()} children={<Info />} />
<SidePanel isOpen={imprintOpen} dismissPanel={() => dismissImprint()} children={<Imprint />} />
<MapContainer zoomControl={false} center={position} zoom={zoom} style={{ height: height }}>
<MoistureMarkers />
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<AppCommandBar openImprint={openImprint} openInfo={openInfo} />
</MapContainer>
</div>
</HistoryWindowContext.Provider>
</QueryClientProvider>
);
}

Expand Down
44 changes: 32 additions & 12 deletions src/components/MoistureMarkers/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import L from "leaflet";
import * as React from "react";
import { useState } from "react";
import { Marker, Popup, useMap } from "react-leaflet";
import { FontWeights, mergeStyleSets, Overlay } from "@fluentui/react";
import "./index.css";
Expand Down Expand Up @@ -65,7 +66,7 @@ const getImageForMeasurement = (record: SensorInfo) => {
const MoistureMarkers: React.FC = () => {
const { loading, error, data } = useMoistureData();
const map = useMap();
const [boundingBox, setBoundingBox] = React.useState<number[][]>([]);
const [boundingBox, setBoundingBox] = useState<number[][]>([]);

if (loading)
return (
Expand Down Expand Up @@ -95,18 +96,37 @@ const MoistureMarkers: React.FC = () => {
setBoundingBox(newBB);
}
}
return (
<>
{data?.records?.map((record: SensorInfo, idx) => (
<Marker key={idx} icon={icon(record)} position={[record.latitude, record.longitude]}>
<Popup className="request-popup">
<SensorTooltip record={record} />
</Popup>
</Marker>
))}
</>
);
return <>{data?.records?.map((record: SensorInfo, idx) => <MoistureMarker key={idx} record={record} />)}</>;
}
};

function MoistureMarker({ record }: { record: SensorInfo }) {
const [open, setOpen] = useState(true);
const ref = React.useRef<number | undefined>();
return (
<Marker
icon={icon(record)}
position={[record.latitude, record.longitude]}
// Leaflet keeps the popup component mounted once the popup has been opened.
// This keeps the query used by the tooltip in use causing spurious fetches for closed popups.
// We manually keep track of the popup and unmount the children to avoid this.
// The timeout is used for smooth fadeout.
eventHandlers={{
popupopen: () => {
if (ref.current) {
window.clearTimeout(ref.current);
ref.current = undefined;
}
setOpen(true);
},
popupclose: () => {
ref.current = window.setTimeout(() => setOpen(false), 1000);
},
}}
>
<Popup className="request-popup">{open ? <SensorTooltip record={record} /> : null}</Popup>
</Marker>
);
}

export default MoistureMarkers;
49 changes: 10 additions & 39 deletions src/hooks/useMoistureData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import useFetch from "use-http";
import { MoistureData, MoistureDataDto } from "../model/models";
import add from "date-fns/add";
import { useCallback, useEffect, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";

const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;
const MOISTURE_DATA_URL = `${BACKEND_URL}/mapData`;
Expand Down Expand Up @@ -37,55 +35,28 @@ function processData(data: MoistureDataDto): MoistureData {

interface MoistureState {
loading: Boolean;
error: Error | undefined;
error: Boolean;
data: MoistureData | undefined;
}

export default function useMoistureData(): MoistureState {
const [signal, setSignal] = useState(true);
const {
loading,
error,
data: dto,
cache,
} = useFetch<MoistureDataDto>(MOISTURE_DATA_URL, { cache: "reload" }, [signal]);
const reload = useCallback(() => {
cache.clear();
setSignal(!signal);
}, [cache, signal]);
const { data: dto, status } = useQuery({
queryKey: ["moisture-data"],
queryFn: async () => fetch(MOISTURE_DATA_URL).then((r) => r.json().then((r) => r as MoistureDataDto)),
});

const validTo = useRef(add(new Date(), { minutes: 5 }));

useEffect(() => {
const checkValid = () => {
if (validTo.current < new Date()) {
reload();
}
};
const checkLive = () => {
const r = window.setInterval(checkValid, 5 * 60 * 1000);
window.addEventListener("blur", () => window.clearInterval(r), {
once: true,
});
};
window.addEventListener("focus", checkValid);
window.addEventListener("focus", checkLive);
return () => {
window.removeEventListener("focus", checkValid);
window.removeEventListener("focus", checkLive);
};
}, [reload]);
const loading = status === "pending";
const error = status === "error";

if (!dto && import.meta.env.DEV) {
return {
loading: false,
error: undefined,
error: false,
data: processData(MOCK_DATA),
};
}

const data = !error && dto ? processData(dto) : undefined;
validTo.current = data ? add(data.timestamp, { days: 1, minutes: 1 }) : add(new Date(), { minutes: 5 });
const data = status === "success" && dto ? processData(dto) : undefined;

return { loading, error, data };
}
14 changes: 9 additions & 5 deletions src/hooks/useSensorDetails.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useFetch from "use-http";
import { SensorDetails, SensorInfo } from "../model/models";
import { useContext } from "react";
import { HistoryWindow, HistoryWindowContext } from "../App";
import { useQuery } from "@tanstack/react-query";

const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;

Expand Down Expand Up @@ -54,7 +54,7 @@ function processData(sensorInfo: SensorInfo, data: SensorDetailsDto): SensorDeta

interface SensorDetailsState {
loading: Boolean;
error: Error | undefined;
error: Boolean;
details: SensorDetails | undefined;
}

Expand All @@ -77,10 +77,14 @@ export default function useSensorDetails(sensorInfo: SensorInfo): SensorDetailsS

const url = `${BACKEND_URL}/sensorData/${sensorInfo.device}?records=${numRecords(historyWindow)}&resolution=${historyWindow}`;

const fetchResult = useFetch<SensorDetailsDto>(url, [sensorInfo]);
const { loading, error, data: dto } = fetchResult;
const { data: dto, status } = useQuery({
queryKey: ["moisture-data", "sensor-details", sensorInfo.device, historyWindow],
queryFn: async () => fetch(url).then((r) => r.json().then((r) => r as SensorDetailsDto)),
});

const details = !error && dto ? processData(sensorInfo, dto) : undefined;
const loading = status === "pending";
const error = status === "error";
const details = status === "success" && dto ? processData(sensorInfo, dto) : undefined;

return { loading, error, details };
}
7 changes: 6 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import "./index.css";
import App from "./App";
import { initializeIcons } from "@fluentui/react";
import { createRoot } from "react-dom/client";
import { StrictMode } from "react";

initializeIcons();

const container = document.getElementById("root");
const root = createRoot(container!);
root.render(<App />);
root.render(
<StrictMode>
<App />
</StrictMode>,
);
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"isolatedModules": true,
/* Type Checking */
"strict": true,
"skipLibCheck": true
"skipLibCheck": true,
},
"include": ["src", "types"]
"include": ["src", "types"],
}

0 comments on commit 5a6306b

Please sign in to comment.