[frontend]: Add debounce to prevent freezing on search
This commit is contained in:
@ -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>
|
||||||
|
Reference in New Issue
Block a user