[frontend]: Fix search input freezing when lots of data
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useMemo } from "react";
|
import React, { useState, useEffect, useMemo, useRef } from "react";
|
||||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
import Navbar from "./components/Navbar";
|
import Navbar from "./components/Navbar";
|
||||||
@ -28,20 +28,26 @@ 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("");
|
||||||
|
const debounceTimeout = useRef(null);
|
||||||
|
|
||||||
// Debounce effect
|
const [filteredMarkers, setFilteredMarkers] = useState([]);
|
||||||
useEffect(() => {
|
const [numMarkers, setNumMarkers] = useState(0);
|
||||||
const handler = setTimeout(() => {
|
|
||||||
setSearchTerm(searchInput);
|
const handleSearchChange = (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
|
||||||
|
// Clear any existing timeout to reset the debounce timer
|
||||||
|
if (debounceTimeout.current) {
|
||||||
|
clearTimeout(debounceTimeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a new timeout to update the state only after 300ms of inactivity
|
||||||
|
debounceTimeout.current = setTimeout(() => {
|
||||||
|
setSearchTerm(value); // Only update state after delay
|
||||||
}, 300);
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(handler);
|
|
||||||
};
|
|
||||||
}, [searchInput]);
|
|
||||||
|
|
||||||
const fetchData = async (enabledSources) => {
|
const fetchData = async (enabledSources) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -282,6 +288,7 @@ function App() {
|
|||||||
})
|
})
|
||||||
.filter((marker) => marker.display);
|
.filter((marker) => marker.display);
|
||||||
|
|
||||||
|
setNumMarkers(newMarkers.length);
|
||||||
setMarkers(newMarkers);
|
setMarkers(newMarkers);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching data:", error);
|
console.error("Error fetching data:", error);
|
||||||
@ -290,15 +297,52 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 2. Memoize the filtered markers so it recalculates only if `searchTerm` or `markers` changes
|
// 2. Memoize the filtered markers so it recalculates only if `searchTerm` or `markers` changes
|
||||||
const filteredMarkers = useMemo(() => {
|
// const filteredMarkers = useMemo(() => {
|
||||||
if (!searchTerm.trim()) {
|
// setLoading(true);
|
||||||
return markers;
|
// console.log("set loading true");
|
||||||
}
|
//
|
||||||
return markers.filter((marker) =>
|
// if (!searchTerm.trim()) {
|
||||||
|
// setLoading(false);
|
||||||
|
// return markers;
|
||||||
|
// }
|
||||||
|
// const newMarkers = markers.filter((marker) =>
|
||||||
|
// marker.markerText.includes(searchTerm.toLowerCase())
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// setLoading(false);
|
||||||
|
// console.log("set loading false");
|
||||||
|
// return newMarkers;
|
||||||
|
//
|
||||||
|
// }, [searchTerm, markers]);
|
||||||
|
|
||||||
|
const memoizedFilteredMarkers = useMemo(() => {
|
||||||
|
return markers.filter(marker =>
|
||||||
marker.markerText.includes(searchTerm.toLowerCase())
|
marker.markerText.includes(searchTerm.toLowerCase())
|
||||||
);
|
);
|
||||||
}, [searchTerm, markers]);
|
}, [searchTerm, markers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (numMarkers > 500) {
|
||||||
|
setLoading(true); // Start loading immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setFilteredMarkers(memoizedFilteredMarkers); // Update markers
|
||||||
|
|
||||||
|
// Now wait 10 seconds before setting loading to false
|
||||||
|
if (numMarkers > 500) {
|
||||||
|
const loadingTimeout = setTimeout(() => {
|
||||||
|
setLoading(false); // Stop loading after 10 seconds
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => clearTimeout(loadingTimeout); // Cleanup loading timeout
|
||||||
|
|
||||||
|
}, 0); // Small debounce before filtering
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout); // Cleanup initial debounce timeout
|
||||||
|
}, [memoizedFilteredMarkers]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@ -320,8 +364,8 @@ function App() {
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={searchInput}
|
// value={searchInput}
|
||||||
onChange={(e) => setSearchInput(e.target.value)}
|
onChange={(e) => handleSearchChange(e)}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
style={{
|
style={{
|
||||||
width: "250px", fontSize: "16px",
|
width: "250px", fontSize: "16px",
|
||||||
|
Reference in New Issue
Block a user