2

I'm a new leaflet user for my Typescript and Hook based React application. In my app my current geolocation position(latitude, longitude) is taken(I've allowed the permission to the browser) using the chrome browser's geolocation API by the app and should be shown in the map with a marker. The issue is, the map is always displayed with the initial default position([0,0]). That is, the new position update taken from geolocation API is not displayed.

The issue might be simple but I couldn't understand what I'm missing here that the Leaflet map is not taking my updated position. I checked, new position values are printed correctly, even I rendered just the updated position values inside a simple div, the new values are rendered correctly. Here is my code as below. Any help is much appreciated.

App.tsx

import React from 'react';
import './App.css';
import { Container } from '@material-ui/core';
import {Route,Switch} from 'react-router-dom';
import Header from './components/Header';
import SideBar from './components/SideBar';
import ShowInGoogleMap from './components/ShowInGoogleMap';
import ShowInLeafletMap from './components/ShowInLeafletMap';


function App() {
  return (
    <div className="app-div">
            <Header/>
            <div className="main-content">
              <SideBar/>
              <div className="map-content">
                <Switch>
                  <Route path="/google-map" component={ShowInGoogleMap}/>
                  <Route path="/leaflet-map" component={ShowInLeafletMap}/>
                </Switch>
              </div>  
            </div>
            

    </div>
    
    
  );
}

export default App;

###############################

ShowInLeafletMap.tsx - this is the function handling Leaflet map

import { LatLngExpression } from 'leaflet';
import React, { useState, useEffect } from 'react';
import 'leaflet/dist/leaflet.css';
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet';

const ShowInLeafletMap = () => {
    const [position, setPosition] = useState<LatLngExpression>([0,0]);

    useEffect(() => {
      if ("geolocation" in navigator) {
        console.log("Available...");
        navigator.geolocation.getCurrentPosition(function(cPosition) {
          console.log("Latitude is :", cPosition.coords.latitude);
          console.log("Longitude is :", cPosition.coords.longitude);
          setPosition([cPosition.coords.latitude,cPosition.coords.longitude]);

        });
      } else {
        console.log("Not Available");
      }


    }, []);

    console.log('this is loaded, ', position);

return(
  
    <MapContainer center={position} zoom={13} scrollWheelZoom={false}>
    <TileLayer
      attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    />
    <Marker position={position}>
      <Popup>
        Hi I just found Dresden
      </Popup>
    </Marker>
  </MapContainer>

    
  
)
} 

export default ShowInLeafletMap;

Here is dependency block of my package.json. I've no dev dependecies for the time being.

"dependencies": {
    "@material-ui/core": "^4.11.4",
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/googlemaps": "^3.43.3",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@types/react-leaflet": "^2.8.1",
    "@types/react-router-dom": "^5.1.7",
    "leaflet": "^1.7.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-leaflet": "^3.2.0",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.3",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1"
  },

2 Answers 2

1

In react-leaflet version 3, most props are immutable. As for the MapContainer itself:

Except for its children, MapContainer props are immutable: changing them after they have been set a first time will have no effect on the Map instance or its container.

You're better off using the whenCreated prop to capture a reference to the underlying L.map instance in a state variable, then using leaflet methods directly:

const ShowInLeafletMap = () => {

  const [map, setMap] = useState<L.Map>()

  useEffect(() => {
    if ("geolocation" in navigator && map) {
      navigator.geolocation.getCurrentPosition(function(cPosition) {
      map.panTo([cPosition.coords.latitude,cPosition.coords.longitude]);
      });
    } 
  }, [map])

  return (
    <MapContainer whenCreated(setMap)>
      ...
    </MapContainer>
  )

}

Working codesandbox

11
  • Thanks much for trying to help me.However, could you please say, what should be the initial value for const [map, setMap] = useState<L.Map>() ? Also I'm getting the error 'current' doesn't exist on type Map. How shall I pass the positions for the marker inside MapContainer element here ? Also if I get different positions over the time, how to render the map with them ? Is it possible to show me a demo ?
    – sandy
    Commented Jul 5, 2021 at 16:37
  • Woops my bad, I was typing it as a ref, which it is not. There will be no initial value, it will be null, which it why your if statement within the useEffect is also dependent on the existence of map. You may need type it as const [map, setMap] = useEffect<L.map | undefined>(undefined) Commented Jul 5, 2021 at 16:41
  • 1
    Well, then also it doesn't work since, 'map' is undefined/null so a panTo method doesn't work on it throwing error "TypeError: Cannot read property 'panTo' of null"
    – sandy
    Commented Jul 5, 2021 at 16:58
  • Did you notice the change to the if statement? You need if ("geolocation" in navigator && map) to make sure the map exists before trying to use any of its methods Commented Jul 5, 2021 at 17:04
  • Yes I changed to if ("geolocation" in navigator && map) but it obviously leads to the else block printing "Not available" since map object is always undefined. Therefore, the if block will never executed and map.panTo will never be called.
    – sandy
    Commented Jul 5, 2021 at 17:09
0

In leaflet v.4 I used this anti-pattern to rerender (wondering about their documentation):

<MapContainer key={new Date().getTime()} ...>

2
  • Did you end up noticing any issues using the MapContainer in this way? It would be nice to have a controlled component in the traditional way but worried that there would be bugs or inconsistencies using it this way
    – NJRBailey
    Commented Apr 29, 2023 at 14:48
  • 1
    It's a hobby project without real that much traffic, but for now works fine. You can try here (no English version, but just click any city from the 1st filter to see the map) - topmoto.pro/ua/services Commented Apr 29, 2023 at 14:55

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