Leaflet map is either already initialized or map container is not found

Viewed 318

Hello friendly developers,

I have a problem with my leaflet map. I initialize like this in a file map.ts, and my page is working like a charm.

import L from 'leaflet';
import 'leaflet.markercluster/dist/leaflet.markercluster'
import { MAP_TITLE_LAYER, MAP_TITLE_ATTRIBUTION, MAP_TARGET } from '../../constants';

// @ts-ignore
import Popup from 'components/MapPopup/index.html.twig';

const markers = L.markerClusterGroup();
let markersData = [];

let map = L.map(MAP_TARGET);

const removeClassFromMarker = () =>{
    const markers = document.querySelectorAll('.leaflet-marker-icon');
    markers.forEach(marker => {
        marker.classList.remove('bring-to-front');
    });
}

export const initMap = (arrayDatas) => {
    if (document.querySelector('#leaflet')) {

        L.tileLayer(MAP_TITLE_LAYER, {
            detectRetina: true,
            minZoom: 1,
            maxZoom: 18,
            attribution: MAP_TITLE_ATTRIBUTION,
        }).addTo(map);

        if (arrayDatas.length) {
            arrayDatas.forEach(item => {
                const position = [
                    item.coordinates.lat,
                    item.coordinates.long,
                ];

                let icon = '';
                const status = item.status;
                switch (status) {
                    case 'excellent':
                        icon = '/assets/images/map/level-1.svg';
                        break;
                    case 'good':
                        icon = '/assets/images/map/level-2.svg';
                        break;
                    case 'average':
                        icon = '/assets/images/map/level-3.svg';
                        break;
                    case 'low':
                        icon = '/assets/images/map/level-4.svg';
                        break;
                    case 'very-bad':
                        icon = '/assets/images/map/level-5.svg';
                        break;
                    default:
                        icon = '/assets/images/map/default.svg'
                }

                const defaultIcon = L.icon({
                    iconUrl: icon,
                    iconSize: [30, 40],
                });
                markersData.push(L.latLng(position));

                // const marker = L.marker(position, { icon: defaultIcon }).on('click', onMarkerClick).bindPopup(Popup({
                const marker = L.marker(position, { icon: defaultIcon })
                .on('click', function(e){
                    removeClassFromMarker;
                    e.target._icon.classList.add('bring-to-front');
                })
                .bindPopup(Popup({
                    name: item.name,
                    localisation : item.localisation,
                    score : item.score,
                    visitors : item.visitors,
                    status : item.status,
                  }));
                markers.addLayer(marker);
            });

            map.addLayer(markers).on('click', function(e){
                removeClassFromMarker;
            });
            var bounds = L.latLngBounds(markersData)
            map.fitBounds(bounds)
            map.zoomControl.setPosition('bottomright');

        }
    }
}

export const deconstructMap = () => {
    markers.clearLayers();
    markersData = [];
}

But on my page, I also have an ajax search, where i am looking for some datas and i am refreshing my map by deconstructing it and calling again the function with rhe right datas, and it's also working well.

import debounce from 'lodash/debounce';
import { getEhpadScoreList, getEhpadList } from './searchEhpad.ajax';

// @ts-ignore
import ResultList from 'components/SearchItem/index.html.twig';
import { LOADER_CLASSNAME, MAP_TARGET } from '../constants';
import { initMap, deconstructMap } from './visitor/map';

const $searchResultContainer = document.getElementById('js-ehpad-search-result');

const minimumSizeTextInput:number = 2;

const dataArray = []

// Loader
const setLoaderOnInput = (item:HTMLInputElement):void =>{
    item.closest('.input-with-loader').classList.add(LOADER_CLASSNAME);
}
const removeLoaderOnInput = (item:HTMLInputElement):void =>{
    item.closest('.input-with-loader').classList.remove(LOADER_CLASSNAME);
}

// When searching
const InputSearchValue = async(e:any):Promise<void> => {
    if (e.target.value.length > minimumSizeTextInput){
        const getScore = Boolean(e.target.getAttribute('data-search-score'));
        try {
            setLoaderOnInput(e.target);
            const data:any = getScore?await getEhpadScoreList(e.target.value):await getEhpadList(e.target.value);
            $searchResultContainer.innerHTML = ResultList({
              items: data,
              translations : JS_TRANSLATION,
            });
            if (document.getElementById(MAP_TARGET)){
                deconstructMap();
                initMap(data);
            }
        } catch (error) {
            console.log(error)
        } finally {
            removeLoaderOnInput(e.target);
        }
    }
}

const DebounceInputSearchValue = debounce(InputSearchValue, 500)

const initSearch = () => {
    const $searchInput = <HTMLInputElement>(document.getElementById('js-input-search-ehpad'));
    if($searchInput){
        $searchInput.addEventListener('input', DebounceInputSearchValue)
    }
}

export default initSearch;

But the problem is, now if i go on another page, I have an error saying that map container is not defined. I tried to move the line let map = L.map(MAP_TARGET); in my initMap function, but if i do that, my ajax search is not working, because i have an error saying that my map is already initialized (it's normal because i'm calling l.map twice).

I'm also thinking that maybe the error is coming from my import, if i could put a condition or something like this, maybe i could fix this issue.

I don't know how to errase the errors, in both case. It's either my map is initialized and i can't search or i have other functionnalities on others pages blocked, i need help !

EDIT : I fixed my issue by adding these two lines

const $mapContainer = document.getElementById(MAP_TARGET);
let map = $mapContainer !== null && L.map($mapContainer);

Thanks a lot !

3 Answers

From your description:

if i go on another page, I have an error saying that map container is not defined

And:

maybe the error is coming from my import, if i could put a condition or something like this

It sounds like you import your map.ts file in the other page as well? (Or possibly indirectly by importing another script that itself imports map.ts, e.g. if you also import your search script)

Then the first error message is because in that page, you do not have a MAP_TARGET element, but since the code in map.ts file is executed, it tries to find it to instantiate your map.

Of course, the proper solution is to not import your map.ts file in pages which are not supposed to have a map.

If for some reason that is too complex, a possible workaround could be indeed to add a condition before instantiating the map.

For example, explicitly look for your MAP_TARGET element first:

const $mapContainer = document.getElementById(MAP_TARGET);
let map = $mapContainer && L.map($mapContainer);

Do something like this

var map = null;
function view_map_area() {

    if (map !== undefined && map !== null)
    {
        map.off();
        map.remove();
    }
    
    map = L.map('map').setView([centerLat,centerLong], 10);
}

Those who are using ".map()" of "rxjs", also may face this error. How I solved it is, importing "map" module of "rxjs" as

import {map as rxjsMap} from 'rxjs/operators';
Related