From e6e19ebaeabe52365205f4914dbbce44a670000f Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:19:27 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20map=20black=20screen=20=E2=80=94=20move?= =?UTF-8?q?=20Leaflet=20CSS=20to=20main.jsx,=20add=20ErrorBoundary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leaflet CSS inside a lazy component has a load-order race with the map initialization. Moving it to main.jsx ensures it's always ready. Also adds a MapErrorBoundary so any Leaflet crash shows an error message instead of unmounting the whole app (black screen), and filters out any non-finite GPS coordinates before passing them to the map. Co-Authored-By: Claude Sonnet 4.6 --- client/src/components/MapView.jsx | 32 ++++++++++++++++++++++++------- client/src/main.jsx | 1 + 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/client/src/components/MapView.jsx b/client/src/components/MapView.jsx index 481c2eb..b1ac461 100644 --- a/client/src/components/MapView.jsx +++ b/client/src/components/MapView.jsx @@ -1,10 +1,22 @@ -import { useEffect, useState, useMemo } from 'react'; +import { useEffect, useState, useMemo, Component } from 'react'; import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-cluster'; import L from 'leaflet'; -import 'leaflet/dist/leaflet.css'; import { fetchMapPhotos } from '../api/photos.js'; +class MapErrorBoundary extends Component { + state = { error: null }; + static getDerivedStateFromError(e) { return { error: e }; } + render() { + if (this.state.error) return ( +
+ Map failed to load: {this.state.error.message} +
+ ); + return this.props.children; + } +} + // Custom dot icon — avoids Leaflet's broken default asset URLs in Vite const dotIcon = (color = '#4d9eff') => L.divIcon({ @@ -37,10 +49,14 @@ export default function MapView({ onOpenPhoto }) { .catch(e => setError(e.message)); }, []); + const validPoints = useMemo(() => + (points ?? []).filter(p => Number.isFinite(p.lat) && Number.isFinite(p.lon)), + [points]); + const center = useMemo(() => { - if (!points?.length) return [20, 0]; - return centroid(points); - }, [points]); + if (!validPoints.length) return [20, 0]; + return centroid(validPoints); + }, [validPoints]); if (error) return (

{error}

@@ -50,7 +66,7 @@ export default function MapView({ onOpenPhoto }) {

Loading map…

); - if (points.length === 0) return ( + if (validPoints.length === 0) return (

No geotagged photos.

Photos with GPS metadata will appear here once indexed.

@@ -58,6 +74,7 @@ export default function MapView({ onOpenPhoto }) { ); return ( +
- {points.map(p => ( + {validPoints.map(p => (
@@ -101,5 +118,6 @@ export default function MapView({ onOpenPhoto }) {
+ ); } diff --git a/client/src/main.jsx b/client/src/main.jsx index d9cc93c..3492447 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,6 +1,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; +import 'leaflet/dist/leaflet.css'; import App from './App.jsx'; createRoot(document.getElementById('root')).render(