Gate map layers behind onLoad to fix missing markers

Source/Layer components mounted before the map style finishes loading
fail silently. Add mapLoaded state + onLoad callback to LocationMap,
NodeLocationMap, and NetworkMap so GeoJSON sources and layers are only
added after the style is ready.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel Pupius
2026-03-15 23:46:03 +00:00
parent 7e173c1b63
commit 19a81a363e
3 changed files with 110 additions and 97 deletions

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState, useEffect, useMemo } from "react";
import React, { useRef, useState, useEffect, useMemo, useCallback } from "react";
import ReactMap, { Source, Layer } from "react-map-gl/maplibre";
import type { FeatureCollection } from "geojson";
import "maplibre-gl/dist/maplibre-gl.css";
@@ -26,6 +26,8 @@ export const LocationMap: React.FC<LocationMapProps> = ({
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
const [mapLoaded, setMapLoaded] = useState(false);
const handleLoad = useCallback(() => setMapLoaded(true), []);
// Only mount the WebGL map when the container enters the viewport.
// This prevents exhausting the browser's WebGL context limit (~8-16)
@@ -79,7 +81,10 @@ export const LocationMap: React.FC<LocationMapProps> = ({
initialViewState={{ longitude, latitude, zoom: effectiveZoom }}
style={{ width: "100%", height: "100%" }}
attributionControl={{ compact: true }}
onLoad={handleLoad}
>
{mapLoaded && (
<>
{showAccuracyCircle && (
<Source id="circle" type="geojson" data={circleGeoJSON}>
<Layer id="circle-fill" type="fill" paint={{ "fill-color": "#4ade80", "fill-opacity": 0.15 }} />
@@ -98,6 +103,8 @@ export const LocationMap: React.FC<LocationMapProps> = ({
}}
/>
</Source>
</>
)}
</ReactMap>
)}

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useMemo, useState } from "react";
import ReactMap, { Source, Layer } from "react-map-gl/maplibre";
import type { FeatureCollection } from "geojson";
import "maplibre-gl/dist/maplibre-gl.css";
@@ -30,14 +30,12 @@ export const NodeLocationMap: React.FC<NodeLocationMapProps> = ({
}) => {
const accuracyMeters = calculateAccuracyFromPrecisionBits(precisionBits);
const effectiveZoom = zoom ?? calculateZoomFromAccuracy(accuracyMeters);
const showCenterDot = true;
const [mapLoaded, setMapLoaded] = useState(false);
const markerGeoJSON = useMemo((): FeatureCollection => ({
type: "FeatureCollection",
features: showCenterDot
? [{ type: "Feature", geometry: { type: "Point", coordinates: [lng, lat] }, properties: {} }]
: [],
}), [lat, lng, showCenterDot]);
features: [{ type: "Feature", geometry: { type: "Point", coordinates: [lng, lat] }, properties: {} }],
}), [lat, lng]);
const circleGeoJSON = useMemo((): FeatureCollection => ({
type: "FeatureCollection",
@@ -59,7 +57,10 @@ export const NodeLocationMap: React.FC<NodeLocationMapProps> = ({
initialViewState={{ longitude: lng, latitude: lat, zoom: effectiveZoom }}
style={{ width: "100%", height: "100%" }}
attributionControl={{ compact: true }}
onLoad={() => setMapLoaded(true)}
>
{mapLoaded && (
<>
<Source id="circle" type="geojson" data={circleGeoJSON}>
<Layer
id="circle-fill"
@@ -86,6 +87,8 @@ export const NodeLocationMap: React.FC<NodeLocationMapProps> = ({
}}
/>
</Source>
</>
)}
</ReactMap>
</div>
);

View File

@@ -187,7 +187,9 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
onZoomStart={handleUserInteraction}
onLoad={() => setMapLoaded(true)}
>
{/* Topology links — always mounted, visibility controlled via layout property */}
{/* Sources and layers — only after map style has loaded */}
{mapLoaded && (
<>
<Source id="links" type="geojson" data={linksGeoJSON}>
<Layer
id="links-line"
@@ -205,7 +207,6 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
/>
</Source>
{/* Node circles */}
<Source id="nodes" type="geojson" data={nodesGeoJSON}>
<Layer
id="nodes-circles"
@@ -236,6 +237,8 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
}}
/>
</Source>
</>
)}
{/* Node popup */}
{selectedNode && (