Pattern classique dans une carte Leaflet avec gestion de zones polygonales rechargées depuis une API : on charge, on dessine, on rafraîchit périodiquement, on supprime.
const zonesLayer = new L.FeatureGroup().addTo(map);
const zoneById = new Map();
async function loadZones() {
// Cleanup avant rechargement
for (const { layer } of zoneById.values()) zonesLayer.removeLayer(layer);
zoneById.clear();
for (const z of zones) {
const layer = L.geoJSON(z.geometry, { style: ... });
layer.eachLayer(l => {
zonesLayer.addLayer(l); // ← on ajoute les ENFANTS
l.on('click', () => focus(z.id));
});
zoneById.set(z.id, { layer, data: z }); // ← on garde le PARENT en référence
}
}
Logique apparente cohérente. L’utilisateur supprime une zone → loadZones() est rappelée → la zone devrait disparaître de la carte.
Sauf qu’elle ne disparaît pas.
Symptôme
Les polygones des zones supprimées restent affichés indéfiniment. Aucune erreur console. L’API renvoie bien la nouvelle liste sans la zone. loadZones() est bien rappelée, zoneById.clear() exécuté. Et pourtant la couche reste à l’écran.
Cause
L.geoJSON() ne retourne pas un layer atomique — c’est une L.FeatureGroup (sous-classe de L.LayerGroup) qui contient les couches individuelles (un layer par feature du GeoJSON).
Quand on fait layer.eachLayer(l => zonesLayer.addLayer(l)), on ajoute les enfants à zonesLayer. Le parent L.geoJSON n’est jamais ajouté à zonesLayer.
Donc au cleanup : zonesLayer.removeLayer(layer) cherche layer (le parent) dans zonesLayer.getLayers(), ne le trouve pas, et return silencieusement sans rien faire. Les enfants ajoutés directement restent en place.
C’est asymétrique : on a ajouté A et B (les enfants), on tente de retirer C (le parent). C n’a jamais été ajouté, retrait = no-op.
Solution
Cohérence : si on référence le parent dans le tracking, ajouter le parent à zonesLayer (la FeatureGroup propage addTo(map) à ses enfants automatiquement) :
for (const z of zones) {
const layer = L.geoJSON(z.geometry, { style: ... });
layer.eachLayer(l => l.on('click', () => focus(z.id))); // handlers seulement
zonesLayer.addLayer(layer); // ← parent dans zonesLayer
zoneById.set(z.id, { layer, data: z });
}
// cleanup OK : removeLayer(parent) retire tous les enfants en cascade
for (const { layer } of zoneById.values()) zonesLayer.removeLayer(layer);
Lessons learned
L.geoJSON()retourne uneL.FeatureGroup, pas un layer simple — chaque feature du GeoJSON devient un enfant individuel dans le groupeaddLayeretremoveLayerne se compensent que sur le MÊME objet : si tu add les enfants, retire les enfants ; si tu add le parent, retire le parentremoveLayersur un layer absent du container = no-op silencieux — pas d’erreur, pas de warning console. Le bug est invisible jusqu’au test fonctionnel- Symptôme typique : couches qui s’accumulent sans jamais disparaître, alors que le code « ajoute puis retire » semble symétrique
- Diagnostic en 1 ligne :
console.log(zonesLayer.getLayers().length)avant/après cleanup — si le count ne descend pas, tonremoveLayercible le mauvais objet - Convention sécurisée : toujours
zonesLayer.addLayer(L.geoJSON(...))(ajouter le parent), et faire leeachLayeruniquement pour attacher des handlers ou styles aux enfants