diff --git a/apps/planner/app/components/PlannerMap.tsx b/apps/planner/app/components/PlannerMap.tsx index 316a9e6..c04cc39 100644 --- a/apps/planner/app/components/PlannerMap.tsx +++ b/apps/planner/app/components/PlannerMap.tsx @@ -235,11 +235,14 @@ function NoGoAreaButton({ active, onClick }: { active: boolean; onClick: () => v function PoiRefresher({ poiState }: { poiState: ReturnType }) { const map = useMap(); + const refreshRef = useRef(poiState.refresh); + refreshRef.current = poiState.refresh; + useEffect(() => { const refresh = () => { const bounds = map.getBounds(); const zoom = map.getZoom(); - poiState.refresh({ + refreshRef.current({ south: bounds.getSouth(), west: bounds.getWest(), north: bounds.getNorth(), @@ -247,9 +250,25 @@ function PoiRefresher({ poiState }: { poiState: ReturnType }) { }, zoom); }; map.on("moveend", refresh); - refresh(); + // Don't call refresh() immediately — let moveend trigger it return () => { map.off("moveend", refresh); }; - }, [map, poiState.refresh]); + }, [map]); + + // Trigger refresh when categories change (but not on mount) + const prevCategories = useRef(poiState.enabledCategories); + useEffect(() => { + if (prevCategories.current === poiState.enabledCategories) return; + prevCategories.current = poiState.enabledCategories; + const bounds = map.getBounds(); + const zoom = map.getZoom(); + poiState.refresh({ + south: bounds.getSouth(), + west: bounds.getWest(), + north: bounds.getNorth(), + east: bounds.getEast(), + }, zoom); + }, [map, poiState.enabledCategories, poiState.refresh]); + return null; } diff --git a/apps/planner/app/lib/use-pois.ts b/apps/planner/app/lib/use-pois.ts index a64f129..dd428b6 100644 --- a/apps/planner/app/lib/use-pois.ts +++ b/apps/planner/app/lib/use-pois.ts @@ -4,8 +4,9 @@ import { getCached, setCached } from "./poi-cache.ts"; import { poiCategories } from "./poi-categories.ts"; const MIN_ZOOM = 12; -const DEBOUNCE_MS = 500; -const BACKOFF_BASE_MS = 5000; +const DEBOUNCE_MS = 1000; +const MIN_REQUEST_INTERVAL_MS = 3000; +const BACKOFF_BASE_MS = 10000; const MAX_BACKOFF_MS = 60000; export type PoiStatus = "idle" | "loading" | "loaded" | "zoom_too_low" | "rate_limited" | "error"; @@ -26,6 +27,7 @@ export function usePois(): PoiState { const abortRef = useRef(null); const debounceRef = useRef>(undefined); const backoffRef = useRef(0); + const lastRequestRef = useRef(0); const toggleCategory = useCallback((id: string) => { setEnabledCategories((prev) => @@ -60,11 +62,16 @@ export function usePois(): PoiState { return; } + // Calculate delay: debounce + respect minimum interval + const sinceLastRequest = Date.now() - lastRequestRef.current; + const delay = Math.max(DEBOUNCE_MS, MIN_REQUEST_INTERVAL_MS - sinceLastRequest); + debounceRef.current = setTimeout(async () => { // Cancel previous request abortRef.current?.abort(); const controller = new AbortController(); abortRef.current = controller; + lastRequestRef.current = Date.now(); setStatus("loading"); @@ -89,7 +96,7 @@ export function usePois(): PoiState { setStatus("error"); } } - }, DEBOUNCE_MS); + }, delay); }, [enabledCategories], );