fix: prevent map freeze and fix repositioning race condition
This commit is contained in:
@@ -112,45 +112,41 @@ function FitBounds({ coords, mapState, end, duration, loading }) {
|
||||
const prevEndRef = useRef();
|
||||
const prevDurationRef = useRef();
|
||||
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(() => {
|
||||
const prevEnd = prevEndRef.current;
|
||||
const prevDuration = prevDurationRef.current;
|
||||
|
||||
// Run only after initial render where refs are populated.
|
||||
if (prevEnd && prevDuration) {
|
||||
const endChanged = end.unix() !== prevEnd.unix();
|
||||
const durationChanged = duration.id !== prevDuration.id;
|
||||
// Don't run on the very first render, wait until refs are populated.
|
||||
if (prevEndRef.current && prevDurationRef.current) {
|
||||
const endChanged = end.unix() !== prevEndRef.current.unix();
|
||||
const durationChanged = duration.id !== prevDurationRef.current.id;
|
||||
|
||||
if (endChanged || durationChanged) {
|
||||
refitNeeded.current = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update refs for the next render's comparison.
|
||||
prevEndRef.current = end;
|
||||
prevDurationRef.current = 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(() => {
|
||||
// Do not run this effect if new data is being fetched.
|
||||
if (loading) return;
|
||||
// Don't do anything while new data is being fetched.
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A refit is needed on initial load (mapState.center is null)
|
||||
// or if the flag has been set by the other effect.
|
||||
if ((mapState.center === null || refitNeeded.current) && coords.length > 0) {
|
||||
// Perform fit on initial load OR if a refit has been flagged.
|
||||
if ((isInitialLoad.current || refitNeeded.current) && coords.length > 0) {
|
||||
const bounds = leaflet.latLngBounds(coords);
|
||||
if (bounds.isValid()) {
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
// Reset the flag after the fit is performed.
|
||||
// Reset flags after fitting.
|
||||
refitNeeded.current = false;
|
||||
isInitialLoad.current = false;
|
||||
}
|
||||
}, [coords, mapState.center, map, loading]);
|
||||
|
||||
return null;
|
||||
}, [coords, loading, map]); // Dependencies: run when data is ready.
|
||||
}
|
||||
|
||||
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 coords = useMemo(() => {
|
||||
if (!Array.isArray(data)) return [];
|
||||
// 1. Guard against invalid top-level data
|
||||
if (!Array.isArray(data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = [];
|
||||
const [startTime, endTime] = range;
|
||||
|
||||
// 2. Loop through the data
|
||||
for (const point of data) {
|
||||
// Ensure the point and its time exist before checking the range
|
||||
if (!point || !point.time) continue;
|
||||
|
||||
if (point.time >= startTime && point.time <= endTime) {
|
||||
const { lat, lon } = point;
|
||||
// Strictest possible check for valid, finite, numeric coordinates
|
||||
if (typeof lat === 'number' && typeof lon === 'number' && isFinite(lat) && isFinite(lon)) {
|
||||
result.push([lat, lon]);
|
||||
// 3. Guard against malformed points
|
||||
if (!point || typeof point !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { lat, lon, time } = point;
|
||||
|
||||
// 4. Guard against invalid time
|
||||
if (typeof time !== 'string' || time.length === 0) {
|
||||
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;
|
||||
|
Reference in New Issue
Block a user