Add error handling for Google Maps DOM conflicts with React

The Google Maps API and React both manipulate the DOM, which can cause
conflicts where Google Maps removes nodes that React still has references
to. Add graceful error handling:

1. Remove async flag from Google Maps script - use defer only
2. Add try-catch in marker update useEffect to ignore removeChild errors
3. Add cleanup function to properly tear down markers on unmount
4. Log warnings instead of crashing when DOM conflicts occur

This allows the app to continue functioning even when there are minor
DOM reconciliation issues between Google Maps and React.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2025-11-04 19:37:27 -06:00
parent 8e52f3fb0e
commit 715250d50d
2 changed files with 35 additions and 12 deletions

View File

@@ -82,20 +82,34 @@ export const StationMap: React.FC<StationMapProps> = ({
};
initMap();
// Cleanup: clear markers when component unmounts
return () => {
try {
markers.current.forEach((marker) => marker.setMap(null));
infoWindows.current.forEach((iw) => iw.close());
markers.current = [];
infoWindows.current = [];
} catch (err) {
// Silently ignore cleanup errors - they don't affect the user
console.debug('[StationMap] Cleanup error (ignored):', err);
}
};
}, []);
// Update markers when stations or saved status changes
useEffect(() => {
if (!map.current) return;
// Clear old markers and info windows
markers.current.forEach((marker) => marker.setMap(null));
infoWindows.current.forEach((iw) => iw.close());
markers.current = [];
infoWindows.current = [];
try {
// Clear old markers and info windows
markers.current.forEach((marker) => marker.setMap(null));
infoWindows.current.forEach((iw) => iw.close());
markers.current = [];
infoWindows.current = [];
getGoogleMapsApi();
let allMarkers: google.maps.Marker[] = [];
getGoogleMapsApi();
let allMarkers: google.maps.Marker[] = [];
// Add station markers
stations.forEach((station) => {
@@ -132,9 +146,19 @@ export const StationMap: React.FC<StationMapProps> = ({
allMarkers.push(currentLocationMarker.current);
}
// Fit bounds to show all markers
if (allMarkers.length > 0) {
fitBoundsToMarkers(map.current, allMarkers);
// Fit bounds to show all markers
if (allMarkers.length > 0) {
fitBoundsToMarkers(map.current, allMarkers);
}
} catch (error) {
// Ignore DOM errors from Google Maps conflicting with React reconciliation
// This is a known issue when Google Maps removes DOM nodes that React still has references to
if (error instanceof Error && error.message.includes('removeChild')) {
console.warn('[StationMap] Ignoring Google Maps DOM conflict:', error.message);
return;
}
// Re-throw other errors
throw error;
}
}, [stations, savedPlaceIds, currentLocation, onMarkerClick]);

View File

@@ -51,8 +51,7 @@ export function loadGoogleMaps(): Promise<void> {
// Create script tag with callback
const script = document.createElement('script');
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&loading=async&callback=${callbackName}&libraries=places`;
script.async = true;
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=${callbackName}&libraries=places`;
script.defer = true;
script.onerror = () => {