[frontend]: Add debounce to prevent freezing on search

This commit is contained in:
2025-03-04 10:35:10 +00:00
parent 09622fca70
commit f97f81d3a8

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useEffect, useMemo } from "react";
import Sidebar from "./components/Sidebar"; import Sidebar from "./components/Sidebar";
import MapComponent from "./components/MapComponent"; import MapComponent from "./components/MapComponent";
import LoadingOverlay from "./components/LoadingOverlay"; import LoadingOverlay from "./components/LoadingOverlay";
@ -21,8 +21,21 @@ function App() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [clusteringEnabled, setClusteringEnabled] = useState(true); const [clusteringEnabled, setClusteringEnabled] = useState(true);
// Search states: one is the raw user input, the other is the actual term we filter on
const [searchInput, setSearchInput] = useState("");
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
// Debounce effect
useEffect(() => {
const handler = setTimeout(() => {
setSearchTerm(searchInput);
}, 300); // Adjust this delay as desired
return () => {
clearTimeout(handler);
};
}, [searchInput]);
const fetchData = async () => { const fetchData = async () => {
setLoading(true); setLoading(true);
try { try {
@ -41,7 +54,6 @@ function App() {
switch (item.objectType) { switch (item.objectType) {
case "IrishRailTrain": case "IrishRailTrain":
objectTitle = "Irish Rail Train: " + item.trainCode; objectTitle = "Irish Rail Train: " + item.trainCode;
let trainType; let trainType;
switch (item.trainType) { switch (item.trainType) {
case "M": case "M":
@ -82,7 +94,6 @@ function App() {
icon += "Late"; icon += "Late";
} }
// Build the popup UI
popupContent = ( popupContent = (
<div> <div>
<h3>{objectTitle}</h3> <h3>{objectTitle}</h3>
@ -97,13 +108,7 @@ function App() {
</div> </div>
); );
markerText = `Irish Rail Train: ${item.trainCode} markerText = item.trainPublicMessage + " " + item.trainDirection;
Train Details: ${splitMessage[1].split("(")[0]}
Train Type: ${trainType}
Status: ${trainStatus}
Direction: ${item.trainDirection}
Update: ${splitMessage[2]}
Punctuality: ${punctuality}`;
break; break;
case "IrishRailStation": case "IrishRailStation":
@ -118,10 +123,7 @@ function App() {
</ul> </ul>
</div> </div>
); );
markerText = item.trainStationCode + " " + item.trainStationDesc;
markerText = `Train Station: ${item.trainStationDesc}
ID: ${item.trainStationID}
Code: ${item.trainStationCode}`;
break; break;
case "Bus": case "Bus":
@ -138,11 +140,7 @@ function App() {
</ul> </ul>
</div> </div>
); );
markerText = item.busRouteAgencyName + " " + item.busRouteShortName + " " + item.busRouteLongName;
markerText = `Bus Agency: ${item.busRouteAgencyName}
Route: ${item.busRoute}
Route Short Name: ${item.busRouteShortName}
Route Long Name: ${item.busRouteLongName}`;
break; break;
case "BusStop": case "BusStop":
@ -157,10 +155,7 @@ function App() {
</ul> </ul>
</div> </div>
); );
markerText = item.busStopName;
markerText = `Bus Stop: ${item.busStopName}
ID: ${item.busStopID}
Code: ${item.busStopCode || "N/A"}`;
break; break;
case "LuasStop": case "LuasStop":
@ -180,8 +175,7 @@ function App() {
popupContent = ( popupContent = (
<LuasPopup item={item} objectTitle={objectTitle} luasLine={luasLine} /> <LuasPopup item={item} objectTitle={objectTitle} luasLine={luasLine} />
); );
markerText = `Luas Stop: ${item.luasStopName} markerText = item.luasStopIrishName + " " + item.luasStopName;
Line: ${luasLine}`;
break; break;
default: default:
@ -200,6 +194,7 @@ function App() {
markerText: markerText.toLowerCase(), markerText: markerText.toLowerCase(),
}; };
}); });
setMarkers(newMarkers); setMarkers(newMarkers);
} catch (error) { } catch (error) {
console.error("Error fetching data:", error); console.error("Error fetching data:", error);
@ -207,17 +202,21 @@ function App() {
setLoading(false); setLoading(false);
}; };
const filteredMarkers = markers.filter((marker) => { // 2. Memoize the filtered markers so it recalculates only if `searchTerm` or `markers` changes
const filteredMarkers = useMemo(() => {
if (!searchTerm.trim()) { if (!searchTerm.trim()) {
return true; return markers;
} }
return marker.markerText.includes(searchTerm.toLowerCase()); return markers.filter((marker) =>
}); marker.markerText.includes(searchTerm.toLowerCase())
);
}, [searchTerm, markers]);
return ( return (
<div style={{ height: "100vh", width: "100vw", display: "flex", position: "relative" }}> <div style={{ height: "100vh", width: "100vw", display: "flex", position: "relative" }}>
{loading && <LoadingOverlay />} {loading && <LoadingOverlay />}
{/* SEARCH BOX */}
<div <div
style={{ style={{
position: "absolute", position: "absolute",
@ -229,15 +228,13 @@ function App() {
> >
<input <input
type="text" type="text"
value={searchTerm} value={searchInput}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchInput(e.target.value)}
placeholder="Search..." placeholder="Search..."
style={{ style={{
width: "250px", fontSize: "16px", padding: "6px", width: "250px", fontSize: "16px",
padding: "10px", background: "rgba(255, 255, 255, 0.9)", color: "black", padding: "10px", background: "rgba(255, 255, 255, 0.9)", color: "black",
borderRadius: "10px", borderRadius: "10px", overflow: "hidden"
display: "flex", flexDirection: "column",
alignItems: "center", zIndex: 1000, overflow: "hidden", justifyContent: "center"
}} }}
/> />
</div> </div>