fix: prevent map freeze and fix repositioning race condition

This commit is contained in:
2025-08-14 21:12:50 +00:00
parent b295c3fef0
commit 478dca185e

View File

@@ -112,45 +112,41 @@ function FitBounds({ coords, mapState, end, duration, loading }) {
const prevEndRef = useRef(); const prevEndRef = useRef();
const prevDurationRef = useRef(); const prevDurationRef = useRef();
const refitNeeded = useRef(false); const refitNeeded = useRef(false);
const isInitialLoad = useRef(mapState.center === null);
// Effect 1: Detects changes in `end` or `duration` and sets a flag. // Effect 1: Flag the need for a refit when end/duration changes.
useEffect(() => { useEffect(() => {
const prevEnd = prevEndRef.current; // Don't run on the very first render, wait until refs are populated.
const prevDuration = prevDurationRef.current; if (prevEndRef.current && prevDurationRef.current) {
const endChanged = end.unix() !== prevEndRef.current.unix();
// Run only after initial render where refs are populated. const durationChanged = duration.id !== prevDurationRef.current.id;
if (prevEnd && prevDuration) {
const endChanged = end.unix() !== prevEnd.unix();
const durationChanged = duration.id !== prevDuration.id;
if (endChanged || durationChanged) { if (endChanged || durationChanged) {
refitNeeded.current = true; refitNeeded.current = true;
} }
} }
// Update refs for the next render's comparison.
prevEndRef.current = end; prevEndRef.current = end;
prevDurationRef.current = duration; prevDurationRef.current = duration;
}, [end, duration]); }, [end, duration]);
// Effect 2: Acts on `coords` changes, but only if the flag is set and data isn't loading. // Effect 2: Perform the fit when conditions are right.
useEffect(() => { useEffect(() => {
// Do not run this effect if new data is being fetched. // Don't do anything while new data is being fetched.
if (loading) return; if (loading) {
return;
}
// A refit is needed on initial load (mapState.center is null) // Perform fit on initial load OR if a refit has been flagged.
// or if the flag has been set by the other effect. if ((isInitialLoad.current || refitNeeded.current) && coords.length > 0) {
if ((mapState.center === null || refitNeeded.current) && coords.length > 0) {
const bounds = leaflet.latLngBounds(coords); const bounds = leaflet.latLngBounds(coords);
if (bounds.isValid()) { if (bounds.isValid()) {
map.fitBounds(bounds); map.fitBounds(bounds);
} }
// Reset the flag after the fit is performed. // Reset flags after fitting.
refitNeeded.current = false; refitNeeded.current = false;
isInitialLoad.current = false;
} }
}, [coords, mapState.center, map, loading]); }, [coords, loading, map]); // Dependencies: run when data is ready.
return null;
} }
function Map({end, duration, slider, mapState, setMapState}) { function Map({end, duration, slider, mapState, setMapState}) {
@@ -159,21 +155,36 @@ function Map({end, duration, slider, mapState, setMapState}) {
const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]); const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]);
const coords = useMemo(() => { const coords = useMemo(() => {
if (!Array.isArray(data)) return []; // 1. Guard against invalid top-level data
if (!Array.isArray(data)) {
return [];
}
const result = []; const result = [];
const [startTime, endTime] = range; const [startTime, endTime] = range;
// 2. Loop through the data
for (const point of data) { for (const point of data) {
// Ensure the point and its time exist before checking the range // 3. Guard against malformed points
if (!point || !point.time) continue; if (!point || typeof point !== 'object') {
continue;
}
if (point.time >= startTime && point.time <= endTime) { const { lat, lon, time } = point;
const { lat, lon } = point;
// Strictest possible check for valid, finite, numeric coordinates // 4. Guard against invalid time
if (typeof lat === 'number' && typeof lon === 'number' && isFinite(lat) && isFinite(lon)) { if (typeof time !== 'string' || time.length === 0) {
result.push([lat, lon]); continue;
} }
// 5. Guard against invalid coordinates
if (typeof lat !== 'number' || typeof lon !== 'number' || !isFinite(lat) || !isFinite(lon)) {
continue;
}
// 6. Now that all data is known to be valid, filter by time
if (time >= startTime && time <= endTime) {
result.push([lat, lon]);
} }
} }
return result; return result;