Fix Leaflet map initialization: prevent DOM errors and ensure robust container checks

- Refactored initializeMap to accept the DOM node instead of the ref object
- Updated all checks to use the DOM node directly
- Improved useInitializeMap to only call initializeMap when the container is ready
- Prevents "mapRef.current ist nicht definiert oder nicht im DOM" errors
- Ensures map is only initialized when the container is attached
This commit is contained in:
ISA
2025-08-19 15:56:24 +02:00
parent db147543d9
commit bf4fc95b8e
8 changed files with 106 additions and 47 deletions

View File

@@ -25,4 +25,4 @@ NEXT_PUBLIC_USE_MOCKS=true
NEXT_PUBLIC_BASE_PATH=/talas5 NEXT_PUBLIC_BASE_PATH=/talas5
# Oder leer lassen für direkten Zugriff -> NEXT_PUBLIC_BASE_PATH= # Oder leer lassen für direkten Zugriff -> NEXT_PUBLIC_BASE_PATH=
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.1.316 NEXT_PUBLIC_APP_VERSION=1.1.317

View File

@@ -26,4 +26,4 @@ NEXT_PUBLIC_BASE_PATH=/talas5
# Oder leer lassen für direkten Zugriff -> NEXT_PUBLIC_BASE_PATH= # Oder leer lassen für direkten Zugriff -> NEXT_PUBLIC_BASE_PATH=
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.1.316 NEXT_PUBLIC_APP_VERSION=1.1.317

View File

@@ -206,6 +206,16 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
} }
}; };
//-----------------------------Map Initialisierung---------------- //-----------------------------Map Initialisierung----------------
// Default map options for Leaflet
const mapOptions = {
center: currentCenter,
zoom: currentZoom,
zoomControl: true,
contextmenu: true,
contextmenuWidth: 180,
contextmenuItems: [],
};
useInitializeMap( useInitializeMap(
map, map,
mapRef, mapRef,
@@ -214,7 +224,8 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
setMenuItemAdded, setMenuItemAdded,
addItemsToMapContextMenu, addItemsToMapContextMenu,
hasRights, hasRights,
value => dispatch(setDisabled(value)) value => dispatch(setDisabled(value)),
mapOptions // pass mapOptions
); );
//-------------------------React Hooks-------------------------------- //-------------------------React Hooks--------------------------------

View File

@@ -2,12 +2,58 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { initializeMap } from "../../../utils/initializeMap"; import { initializeMap } from "../../../utils/initializeMap";
const useInitializeMap = (map, mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, setPolylineEventsDisabled) => { const useInitializeMap = (
map,
mapRef,
setMap,
setOms,
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
setPolylineEventsDisabled,
mapOptions
) => {
useEffect(() => { useEffect(() => {
if (mapRef.current && !map) { let cancelled = false;
initializeMap(mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, setPolylineEventsDisabled); function tryInit(firstAttempt = true) {
if (cancelled) return;
// Only try to initialize if mapRef.current is ready and in DOM
if (
mapRef.current &&
mapRef.current instanceof HTMLElement &&
document.body.contains(mapRef.current) &&
!map &&
!mapRef.current._leaflet_id
) {
try {
const result = initializeMap(
mapRef.current, // pass DOM node, not ref
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
setPolylineEventsDisabled,
firstAttempt // log error only on first real attempt
);
if (result && result.map && result.oms) {
setMap(result.map);
setOms(result.oms);
}
} catch (error) {
if (process.env.NODE_ENV === "development") {
// eslint-disable-next-line no-console
console.warn("Map initialization error:", error);
}
}
} else if (!map && !cancelled) {
// If not ready, just retry after a short delay, do not call initializeMap at all
setTimeout(() => tryInit(false), 50);
}
} }
}, [mapRef, map, hasRights, setPolylineEventsDisabled]); tryInit(true);
return () => {
cancelled = true;
};
}, [mapRef, map, hasRights, setPolylineEventsDisabled, mapOptions]);
}; };
export default useInitializeMap; export default useInitializeMap;

View File

@@ -343,7 +343,7 @@ function MapLayersControlPanel() {
).values(), ).values(),
*/} */}
{GisStationsStaticDistrict.Points.filter(p => !!p.Area_Name).map((item, index) => ( {GisStationsStaticDistrict.Points.filter(p => !!p.Area_Name).map((item, index) => (
<option key={item.Area_Name} value={item.IdLD}> <option key={item.Area_Name + "-" + item.IdLD} value={item.IdLD}>
{item.Area_Name} {item.Area_Name}
</option> </option>
))} ))}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "nodemap", "name": "nodemap",
"version": "1.1.316", "version": "1.1.317",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nodemap", "name": "nodemap",
"version": "1.1.316", "version": "1.1.317",
"dependencies": { "dependencies": {
"@emotion/react": "^11.13.3", "@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "nodemap", "name": "nodemap",
"version": "1.1.316", "version": "1.1.317",
"dependencies": { "dependencies": {
"@emotion/react": "^11.13.3", "@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",

View File

@@ -5,48 +5,50 @@ import "leaflet/dist/leaflet.css";
import "leaflet-contextmenu/dist/leaflet.contextmenu.css"; import "leaflet-contextmenu/dist/leaflet.contextmenu.css";
import "overlapping-marker-spiderfier-leaflet"; import "overlapping-marker-spiderfier-leaflet";
export const initializeMap = async ( export const initializeMap = (
mapRef, mapContainer,
setMap,
setOms,
setMenuItemAdded, setMenuItemAdded,
addItemsToMapContextMenu, addItemsToMapContextMenu,
hasRights, hasRights,
setPolylineEventsDisabled setPolylineEventsDisabled,
logError = false
) => { ) => {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH; const basePath = process.env.NEXT_PUBLIC_BASE_PATH;
if (!mapRef.current) { if (
console.error("❌ Fehler: mapRef.current ist nicht definiert."); !mapContainer ||
return; !(mapContainer instanceof HTMLElement) ||
!document.body.contains(mapContainer)
) {
if (logError) {
console.error("❌ Fehler: map container ist nicht definiert oder nicht im DOM.");
}
return null;
} }
// Robuste Entfernung einer evtl. alten Leaflet-Instanz und Reset des DOM-Elements // Robuste Entfernung einer evtl. alten Leaflet-Instanz und Reset des DOM-Elements
if (mapRef.current) { if (mapContainer) {
if (mapRef.current._leaflet_id) { if (mapContainer._leaflet_id) {
try { try {
// Leaflet-Instanz entfernen // Leaflet-Instanz entfernen
if ( if (mapContainer._leaflet_map && typeof mapContainer._leaflet_map.remove === "function") {
mapRef.current._leaflet_map && mapContainer._leaflet_map.remove();
typeof mapRef.current._leaflet_map.remove === "function"
) {
mapRef.current._leaflet_map.remove();
} }
// Leaflet 1.7+ speichert die Map-Instanz in L.Map._instances // Leaflet 1.7+ speichert die Map-Instanz in L.Map._instances
if (L && L.Map && L.Map._instances && mapRef.current._leaflet_id) { if (L && L.Map && L.Map._instances && mapContainer._leaflet_id) {
delete L.Map._instances[mapRef.current._leaflet_id]; delete L.Map._instances[mapContainer._leaflet_id];
} }
// Auch in L.DomUtil._store ggf. entfernen // Auch in L.DomUtil._store ggf. entfernen
if (L && L.DomUtil && L.DomUtil._store && mapRef.current._leaflet_id) { if (L && L.DomUtil && L.DomUtil._store && mapContainer._leaflet_id) {
delete L.DomUtil._store[mapRef.current._leaflet_id]; delete L.DomUtil._store[mapContainer._leaflet_id];
} }
// _leaflet_id vom DOM-Element entfernen // _leaflet_id vom DOM-Element entfernen
delete mapRef.current._leaflet_id; delete mapContainer._leaflet_id;
// Alle weiteren _leaflet-Properties entfernen // Alle weiteren _leaflet-Properties entfernen
for (const key in mapRef.current) { for (const key in mapContainer) {
if (key.startsWith("_leaflet")) { if (key.startsWith("_leaflet")) {
try { try {
delete mapRef.current[key]; delete mapContainer[key];
} catch (e) {} } catch (e) {}
} }
} }
@@ -55,22 +57,24 @@ export const initializeMap = async (
} }
} }
// Container leeren (immer, auch wenn keine Map-Instanz) // Container leeren (immer, auch wenn keine Map-Instanz)
mapRef.current.innerHTML = ""; mapContainer.innerHTML = "";
} }
const url = new URL(window.location.origin); let tileLayerUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
let tileLayerUrl = "";
try { try {
if (window && window.__tileSource) { if (window && window.__tileSource) {
tileLayerUrl = window.__tileSource; tileLayerUrl = window.__tileSource;
} else { } else {
const res = await fetch("/config.json"); // Synchronous config fetch using XMLHttpRequest (since fetch is async)
const config = await res.json(); const xhr = new XMLHttpRequest();
if (config.tileSources && config.active && config.tileSources[config.active]) { xhr.open("GET", "/config.json", false); // false = synchronous
window.__tileSource = config.tileSources[config.active]; xhr.send(null);
tileLayerUrl = window.__tileSource; if (xhr.status === 200) {
// Optional: Map reload oder Layer neu setzen, falls gewünscht const config = JSON.parse(xhr.responseText);
if (config.tileSources && config.active && config.tileSources[config.active]) {
window.__tileSource = config.tileSources[config.active];
tileLayerUrl = window.__tileSource;
}
} }
} }
} catch (e) { } catch (e) {
@@ -79,7 +83,7 @@ export const initializeMap = async (
let initMap; let initMap;
try { try {
initMap = L.map(mapRef.current, { initMap = L.map(mapContainer, {
center: [53.111111, 8.4625], center: [53.111111, 8.4625],
zoom: 12, zoom: 12,
minZoom: 5, minZoom: 5,
@@ -114,10 +118,8 @@ export const initializeMap = async (
nearbyDistance: 20, nearbyDistance: 20,
}); });
setMap(initMap);
setOms(overlappingMarkerSpiderfier);
if (typeof addItemsToMapContextMenu === "function") { if (typeof addItemsToMapContextMenu === "function") {
addItemsToMapContextMenu(initMap, setMenuItemAdded, setPolylineEventsDisabled); addItemsToMapContextMenu(initMap, setMenuItemAdded, setPolylineEventsDisabled);
} }
return { map: initMap, oms: overlappingMarkerSpiderfier };
}; };