57

I have the below react class which fetches the geolocation through the browser.

I am mapping a leaflet map. I want to geolocation to be an input to the setView, for such that the map "zooms" into the region of the client browser location.

Here's the react class:

    import React from 'react';
    import L from 'leaflet';
    import countries from './countries.js'; 

    var Worldmap = React.createClass({

        render: function() {

            let geolocation = [];

            navigator.geolocation.getCurrentPosition(function(position) {
                let lat = position.coords.latitude;
                let lon = position.coords.longitude;
                geolocation.push(lat, lon);
                locationCode()
            });

            function locationCode() {
                if(geolocation.length <= 0)
                    geolocation.push(0, 0);
            }


            let map = L.map('leafletmap').setView(geolocation, 3);

            L.geoJSON(countries, {
                style: function(feature) {
                    return {
                        fillColor: "#FFBB78",
                        fillOpacity: 0.6,
                        stroke: true,
                        color: "black",
                        weight: 2
                    };
                }
            }).bindPopup(function(layer) {
                return layer.feature.properties.name;
            }).addTo(map);

            return (
                <div id="leafletmap" style={{width: "100%", height: "800px" }}/>
            )
        }
    });

    export default Worldmap

It's called in a main file where the HTML is rendered as <WorldMap />.

I get the error Uncaught Error: Map container not found. when loading the page. Looking around, usually it would be because the map is trying to be displayed in a div before being provided values((gelocation, 3) in this case). However, it shouldn't display it before being returned from the render function below.

What could the issue be?

Printing out the geolocation in the console correctly fetches the coordinates, so that doesn't seem to be the issue.

7 Answers 7

98

The <div id="leafletmap"> must be added to the dom before calling L.map('leafletmap').

4
  • How should I approach it. Add the leaflet functions outside of the React class? If I include those in the main.js where the DOM is rendered, I can't access the geolocation variable.
    – cbll
    Commented Mar 7, 2017 at 12:31
  • 6
    See github.com/PaulLeCam/react-leaflet/blob/master/src/Map.js - the call to L.map() is done when the component is mounted, not when the component is rendered. Commented Mar 7, 2017 at 12:50
  • 2
    I can't really make sense of that example compared to mine, due to the way that React class is structured(newer syntax than mine)
    – cbll
    Commented Mar 7, 2017 at 12:57
  • For those who looking for the working source code, scroll down below @hoogw. I had an answer show you how to implement it.
    – hoogw
    Commented May 21, 2021 at 22:11
7

In addition to @IvanSanchez's response, You could add the geolocation and L.map(...) code to componentDidMount() React lifecycle method (depending on what other goals you hope to achieve). You could also create and bind event handlers for location found as well.

This way must have been added the dom and leaflet can find it.

Happy to help with this if it's still unclear.

7

The error caused by

The div id="map" must be added to the dom before calling L.map('map').

The solution is use:

         useEffect(() => {

This is my working app.js :

           import React, { useState, useEffect } from 'react';



           import './App.css';

           import L from 'leaflet';
           import 'leaflet/dist/leaflet.css';


           function App() {






          // Similar to componentDidMount and componentDidUpdate:
          useEffect(() => {



                              let current_lat = 28.625789;
                              let current_long = 77.0547899;
                              let current_zoom = 16;
                              let center_lat = current_lat;
                              let center_long = current_long;
                              let center_zoom = current_zoom;




                              // The <div id="map"> must be added to the dom before calling L.map('map')
                                let map = L.map('map', {
                                  center: [center_lat, center_long],
                                  zoom: center_zoom
                                });

                                L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
                                  attribution: '&copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                                }).addTo(map);



          });





                  return (


              

                                <div class="right-sidebar-container">
                                        
                                  <div id="map">

                                  </div>
                                
                              </div>

                  );





         } // app







     export default App;
2

I hope this helps if you are using Angular:

const container = document.getElementById('map')
if(container) {
    // code to render map here...
}
1
1

I had the same problem on react I solved it by initialised at the top in useEeffect Here is my React Code.

  const  mapContainerRef = useRef(null), 
  useEffect( async () => {
  const res =await Axios.get(BASE_PATH + 'fetchProperty')

  const container = L.DomUtil.get(mapContainerRef.current); if(container != null){ container._leaflet_id = null; }

  if(container) {


  const mapView = L.map( mapContainerRef.current, {
    zoom: 13,
    center: [19.059984, 72.889999]
    //  maxZoom: 13
    // minZoom: 15
  });
  // const canvas = mapView.getCanvasContainer();
  mapView.zoomControl.setPosition("bottomright");
  mapView.attributionControl.addAttribution(
    "<a href='https://mascots.pro'>Mascots. pro</a>"
  );
  L.tileLayer(
    // "https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/{z}/{x}/{y}?access_token=" + https://api.mapbox.com/styles/v1/anonymousmw/cko1eb1r20mdu18qqtps8i03p/tiles/{z}/{x}/{y}?access_token=
    "https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/{z}/{x}/{y}?access_token=" +
      access_token,
    {
      attribution: '<a href="http://mascots.work">Mascots</a>'
    }
  ).addTo(mapView);

  const mask = L.tileLayer.mask(
    "https://api.mapbox.com/styles/v1/anonymousmw/cko1eb1r20mdu18qqtps8i03p/tiles/{z}/{x}/{y}?access_token=" +
      access_token,
    {
      attribution: '<a href="https://mascots.pro">Mascots pro</a>',
      maskSize: 300
      // maxZoom: 18,
      // maxNativeZoom: 16
      // tms: true
    }
  )
  .addTo(mapView);

  mapView.on("mousemove", function (e) {
    mask.setCenter(e.containerPoint);
  });
  res.data.map((marker) => {
  
    const innerHtmlContent = `<div id='popup-container' class='popup-container'> <h3> Property Details</h3>
    <div class='popup-label'>Building Name :<p>${marker.Building}</p></div>
    <div class='popup-address-label'> Address : <p>${marker.Landmark}, ${marker.Location}</p></div>
    <div class='popup-rent-label'>Monthly Rent : <p> ₹ ${marker.Price}</p></div>
    </div>`;
    const divElement = document.createElement("div");
    const assignBtn = document.createElement("div");
    assignBtn.className = "map-link";
    assignBtn.innerHTML = `<button class="view-btn">View Property</button>`;
    divElement.innerHTML = innerHtmlContent;
    divElement.appendChild(assignBtn);
    assignBtn.addEventListener("click", (e) => {
      console.log("dsvsdvb");
    });
    var iconOptions = {
      iconUrl: "/images/location_pin2.svg",
      iconSize: [25, 25]
    };
    var customIcon = L.icon(iconOptions);

    // create popup contents
    var customPopup = divElement;

    // specify popup options
    var customOptions = {
      maxWidth: "500",
      className: "custom"
    };

    const markerOptions = {
      // title: "MyLocation",
      //    draggable: true
      clickable: true,
      icon: customIcon
    };
    const mark = L.marker([marker.Latitude,marker.Longitude], markerOptions);
    mark.bindPopup(customPopup, customOptions);
    mark.addTo(mapView);
    // return mapView.off();
   
  });
  return () => mapView.remove();
}
}, [])

return (
  <div className="map-box">
        <div className="map-container" ref={mapContainerRef}></div>
    </div>
);

}

0

In Angular i had to place it in a ngAfterViewInit, like this:

ngAfterViewInit() {
    this.mymap = L.map('mapid').setView([51.505, -0.09], 13);
}

being this in my "view.component.ts" exported class

0

I had the same problem on React JS and I found the simplest way to implement the @IvanSanchez solution.

You just need to make React execute the <div id="leafletmap"> first, before executing javascript.

To do this, you just need to add the javascript code inside the useEffect hook, because useEffect is asynchronous, it will execute after the synchronous code execution is done.

Here is my code:

import L from 'leaflet'
import 'leaflet/dist/leaflet.css';
import { useEffect } from 'react';

export default function LeafletContainer() {
  useEffect(()=>{
    var map = L.map('map').setView([51.505, -0.09], 13);
    
    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    }).addTo(map);
  })
  
  return(
    <div className="leaflet-container">
      <div id="map"></div>
    </div>
  )
}

Dont forget to add height of the map in css:

#map {
  height: 180px;
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.