From where is leaflet loading data after hot module reload? - javascript

I'm using react-leaflet, and after webpack triggers a hot reload, GeoJSON features remain on the map, but none are present if we check map._layers on load, like this:
map.whenReady(() => console.log(map._layers))
Similarly, using map.eachLayer doesn't work, since leaflet apparently doesn't yet know about the layers.
But if I open the Chrome console and check map._layers manually, the layers are present. Why is this delay happening, and how can I wait for them to load so I can clear them?

If you take a look at the React-Leaflet docs, you can see that the whenReady method doesn't take in the map instance. The whenCreated method, however, does:
If you want to access the map instance once the map has been rendered, you should be using the whenCreated map method rather than the whenReady method.
You can also use the whenCreated method to set the map to state and access it in a useEffect hook, for eaxmple. Here is an example component:
import React, { useState, useEffect } from 'react';
import L from 'leaflet';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
const MapComponent = () => {
const [map, setMap] = useState(null);
const position = [51.505, -0.09];
useEffect(() => {
if (!map) return;
if (map) {
console.log(map.eachLayer((layer) => console.log('USE EFFECT:', layer)));
}
}, [map]);
return (
<>
<MapContainer
center={position}
zoom={13}
scrollWheelZoom={false}
style={{ height: '100vh', width: '100%', padding: 0 }}
whenCreated={(map) => {
console.log('MAP:', map);
console.log(map.eachLayer((layer) => console.log('LAYER:', layer)));
setMap(map);
}}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
</>
);
};
export default MapComponent;
Here is a demo on StackBlitz you can play around with: DEMO
If you want to access the map instance from the useMap or any other hook, you just have to make sure that you are using the hook in a descendant of a MapContainer:
import React, { useState, useEffect } from 'react';
import L from 'leaflet';
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
const ChildOfMapContainer = (props) => {
// Set up the useMap hook in the descendant of MapContainer
const map = useMap();
// Access the map instance:
useEffect(() => {
// map.eachLayer((layer) => console.log('Layer:', layer));
map.whenReady(() => map.eachLayer((layer) => console.log(layer)));
}, [props, map]);
return (
<>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={props.position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</>
);
};
const MapComponent = () => {
const position = [51.505, -0.09];
return (
<>
<MapContainer
center={position}
zoom={13}
scrollWheelZoom={false}
style={{ height: '100vh', width: '100%', padding: 0 }}
>
<ChildOfMapContainer position={position} />
</MapContainer>
</>
);
};
export default MapComponent;
Sandbox with this example

Related

React-leaflet Uncaught TypeError: Cannot read properties of undefined (reading 'marker')

I'm using react-leaflet and I have created a map in which I have already put markers. And I'm just trying to make a function that when the button is clicked, adds a new marker. But I'm getting this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'marker')
Note:
The filterGare1 part and the gare variables correspond to longitude and latitude data retrieved from a JSON file
Here my code:
import "./App.css";
import "leaflet/dist/leaflet.css";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import { iconPerso } from "./iconMarker";
import { map, L} from "leaflet";
import dataGares from "./data/referentiel-gares-voyageurs.json";
const center = [46.227638, 2.213749];
const filterGare1 = dataGares.filter(
(gare) => gare.fields.segmentdrg_libelle === "a"
);
function getLayers(){
var newMarker = new L.marker([42.5020902, 2.1131379])
.addTo(map)
.bindPopup("MARKER TEST");
}
export default function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
let niveau = filterGare1;
return (
<MapContainer
center={center}
zoom={6}
style={{ width: "100vw", height: "100vh" }}
>
<TileLayer
url="https://api.maptiler.com/maps/basic/256/{z}/{x}/{y}.png?key=F4ZxF5g8ioWE3GlTx3i0#-0.2/0.00000/76.51878"
attribution='© MapTiler © OpenStreetMap contributors'
/>
{niveau.map((gare) => (
<Marker
key={gare.recordid}
position={[
gare.fields.latitude_entreeprincipale_wgs84,
gare.fields.longitude_entreeprincipale_wgs84,
]}
icon={iconPerso}
>
<Popup>
<h3> {"Name : " + gare.fields.gare_alias_libelle_noncontraint} </h3>
<button onClick={getLayers}>get Layers</button>
</Popup>
</Marker>
))}
<GetZoom />
</MapContainer>
);
}
I searched for an answer online but none of the proposed solutions solved my problem.
Some talk about markerCluster -> Leaflet 1.7: L.MarkerClusterGroup is not a function
But that doesn't really correspond to what I'm trying to do. I only want to add one marker to my map but I dont't know where my mistake could be, can anyone see it? Thanks in advance
It might be the import of L, this is how I am able to use L
import L, { LeafletMouseEventHandlerFn } from "leaflet";
However you mix leaflet and react-leaflet workflows for marker. I would advise you to just use the react-leaflet tag for all your markers.
Simply add the marker to a state on click and render that state the same way you do with your other markers
export default function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
const [markers, setMarkers] = React.useState([]);
let niveau = filterGare1;
return (
<MapContainer
center={center}
zoom={6}
style={{ width: "100vw", height: "100vh" }}
>
<TileLayer
url="https://api.maptiler.com/maps/basic/256/{z}/{x}/{y}.png?key=F4ZxF5g8ioWE3GlTx3i0#-0.2/0.00000/76.51878"
attribution='© MapTiler © OpenStreetMap contributors'
/>
{niveau.map((gare) => (
<Marker
key={gare.recordid}
position={[
gare.fields.latitude_entreeprincipale_wgs84,
gare.fields.longitude_entreeprincipale_wgs84,
]}
icon={iconPerso}
>
<Popup>
<h3> {"Name : " + gare.fields.gare_alias_libelle_noncontraint} </h3>
<button onClick={() => setMarkers([[42.5020902, 2.1131379]])}>
get Layers
</button>
</Popup>
</Marker>
))}
{markers.map((marker, i) => (
<Marker key={`marker-${i}`} position={marker} />
))}
<GetZoom />
</MapContainer>
);
}
Edit: The setMarkers did just add the position and not the position in an array. Which should have resulted in marker just being a number and not a position. My example is changed to setMarkers([[42.5020902, 2.1131379]].
You could of course use your own object as the the items in the array, this was just to show with the least needed data (position)
Try changing the way you created function getLayers(){...}.
Change it to a Lambda or Arrow function: getLayers = () => {...}

How to move BasemapGallery position in ReactJS Leaflet?

I want to move the position in the Basemap Gallery Leaflet to the top near the Leaflet zoom on the Map but am confused about how to move the coding, here it is:
Basemap Gallery Brochure wants to move up and near Zoom.
Sample image: Images
Example of desired Basemap Gallery position:
Example of Basemap Gallery position
Here is my code and what I need to change in my code:
import { React, useState, useEffect } from 'react'
import {
LayersControl,
MapContainer,
TileLayer,
Marker,
Popup,
useMapEvents,
} from 'react-leaflet'
import List from '../Component/List'
import L from 'leaflet'
import SearchControl from './SearchControl'
const { BaseLayer } = LayersControl
function LocationMarker() {
const [position, setPosition] = useState(null)
const map = useMapEvents({
locationfound(e) {
setPosition(e.latlng)
map.flyTo(e.latlng, map.getZoom())
},
})
return position === null ? null : (
<Marker position={position}>
<Popup>Location Me</Popup>
</Marker>
)
}
function MyLocationMe() {
const [map, setMap] = useState(null)
const [position, setPosition] = useState(null)
useEffect(() => {
if (!map) return
L.easyButton('fa-map-marker', () => {
map.locate().on('locationfound', function (e) {
setPosition(e.latlng)
map.flyTo(e.latlng, map.getZoom())
})
}).addTo(map)
}, [map])
return (
<div className="flex ml-auto">
<List />
<div className="w-4/5">
<MapContainer
center={{ lat: 51.505, lng: -0.09 }}
zoom={20}
style={{ height: '100vh' }}
whenCreated={setMap}
>
<SearchControl className="MarkerIcon" position="topright" />
<LayersControl position="topleft">
<BaseLayer checked name="OpenStreetMap">
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png "
/>
</BaseLayer>
</LayersControl>
<LocationMarker />
</MapContainer>
</div>
</div>
)
}
export default MyLocationMe
The easiest way to do that is to change the left css property of the relevant css class by making position absolute
Add this to your styles.css
.leaflet-control-layers.leaflet-control {
position: absolute;
left: 50px;
}
Demo

How to change button position in reactJS Leaflet?

I want to move the position on the BasemapGallery Leaflet and GeoSearch Leaflet on the Map but for confusion how to move in coding, here is:
The BasemapGallery leaflet wants to move to the right, and
The GeoSearch leaflet wants to move to the left.
Sample image:
Image
Here is my code and what I need to change in my code:
import { React, useState, useEffect } from 'react'
import {
LayersControl,
MapContainer,
TileLayer,
Marker,
Popup,
useMapEvents,
} from 'react-leaflet'
import List from '../Component/List'
import L from 'leaflet'
import SearchControl from './SearchControl'
const { BaseLayer } = LayersControl
function LocationMarker() {
const [position, setPosition] = useState(null)
const map = useMapEvents({
locationfound(e) {
setPosition(e.latlng)
map.flyTo(e.latlng, map.getZoom())
},
})
return position === null ? null : (
<Marker position={position}>
<Popup>Location Me</Popup>
</Marker>
)
}
function MyLocationMe() {
const [map, setMap] = useState(null)
const [position, setPosition] = useState(null)
useEffect(() => {
if (!map) return
L.easyButton('fa-map-marker', () => {
map.locate().on('locationfound', function (e) {
setPosition(e.latlng)
map.flyTo(e.latlng, map.getZoom())
})
}).addTo(map)
}, [map])
return (
<div className="flex ml-auto">
<List />
<div className="w-4/5">
<MapContainer
center={{ lat: 51.505, lng: -0.09 }}
zoom={20}
style={{ height: '100vh' }}
whenCreated={setMap}
>
<SearchControl className="MarkerIcon" />
<LayersControl>
<BaseLayer checked name="OpenStreetMap">
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png "
/>
</BaseLayer>
</LayersControl>
<LocationMarker />
</MapContainer>
</div>
</div>
)
}
export default MyLocationMe
There should be a position attribute for the controls:
<SearchControl className="MarkerIcon" position="topright" />
and
<LayersControl position="topleft">
<BaseLayer checked name="OpenStreetMap">
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png "
/>
</BaseLayer>
</LayersControl>

How do I assign a function to a button from another component in React?

I have a Map component where I created a function to get the user's current location. I have imported this map component into a larger RetailLocations component. I want to assign the handleClick() function created in the Map component to a button in the RetailLocations component. The relevant code:
Map Component code:
const [center, setCenter] = useState({ lat: 0, lng: 0 });
const location = useGeoLocation();
const mapRef = useRef();
const ZOOM_LEVEL = 20;
const handleClick = (e) => {
if (location.loaded) {
mapRef.current.leafletElement.flyTo(
[location.coordinates.lat, location.coordinates.lng],
ZOOM_LEVEL,
{ animate: true }
);
}
};
return <>
<MapContainer
center={center}
zoom={ZOOM_LEVEL}
ref={mapRef}
scrollWheelZoom={false}
className={style.mapContainer}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
{location.loaded && (
<Marker position={[ location.coordinates.lat, location.coordinates.lng ]}></Marker>
)}
</MapContainer>
</>;
};
export default Map;
This is the RetailLocations component's relevant code:
import Map from './Map';
....
<button
className={style.locationBtn}
onClick={handleClick}
>My Location</button>
....
Is there something else I'm missing?
you should read this. from pluralsight: Components are an integral part of React. Each React application consists of several components, and each component may require user interaction that triggers various actions.
To achieve user interactivity, we can call functions and methods to accomplish specific operations in React. We pass data from parent to child or child to parent components using these actions.
https://www.pluralsight.com/guides/how-to-reference-a-function-in-another-component
Use forwardRef and useImperativeHandle hooks to access method inside a functional component.
Map component:
import { forwardRef, useImperativeHandle } from 'react'
const Map = forwardRef((props, ref) => {
...
const handleClick = (e) => {
if (location.loaded) {
mapRef.current.leafletElement.flyTo(
[location.coordinates.lat, location.coordinates.lng],
ZOOM_LEVEL,
{ animate: true }
);
}
};
useImperativeHandle(
ref,
() => ({ handleClick }),
[handleClick]
);
return <>
<MapContainer
center={center}
zoom={ZOOM_LEVEL}
ref={mapRef}
scrollWheelZoom={false}
className={style.mapContainer}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
{location.loaded && (
<Marker position={[ location.coordinates.lat, location.coordinates.lng ]}></Marker>
)}
</MapContainer>
</>;
})
RetailLocations component:
import Map from './Map';
import { useRef } from 'react';
....
const RetailLocations = () => {
const ref = useRef()
return <>
<Map ref={ref} />
<button
className={style.locationBtn}
onClick={(e) => ref.current.handleClick(e)}>
My Location
</button>
</>
}
....

How to find lat/long properties in leaflet React

I've been trying to integrate leaflet into my react app. Can't figure out how to read properties from the marker. I managed to do it in vanilla JavaScript, but can't apply it to React JS project. How do i find lat and long properties of a draggable marker?
import React, { useState, useEffect } from "react";
import { Map as LeafletMap, TileLayer, Marker, Popup } from "react-leaflet";
import RangeSlider from "react-bootstrap-range-slider";
import L from "leaflet";
const MapComponent = () => {
const [position, setPosition] = useState({
lat: 51.505,
lng: -0.09,
});
const [value, setValue] = useState();
return (
<div>
<LeafletMap
center={[50, 10]}
zoom={6}
maxZoom={10}
attributionControl={true}
zoomControl={true}
doubleClickZoom={true}
scrollWheelZoom={true}
dragging={true}
animate={true}
easeLinearity={0.35}
>
<TileLayer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" />
<Marker position={[50, 10]} draggable>
<Popup>Popup for any custom information.</Popup>
</Marker>
</LeafletMap>
<RangeSlider
value={value}
className="slider"
max={2020}
onChange={(e) => setValue(e.target.value)}
/>
</div>
);
};
export default MapComponent;
Listen to marker's onDragEnd event to be able to extract the new marker coordinates:
<Marker
position={[50, 10]}
draggable
icon={icon}
ondragend={handleDragEnd}
>
...
const handleDragEnd = (e) => {
const { lat, lng } = e.target.getLatLng();
console.log(`Lat: ${lat}, Lon: ${lng}`);
};
Demo
Actually, I imported leaflet library to use the icon on my Marker:
import Leaflet from 'leaflet';
import mapMarkerImg from "../Smile-map.svg";
const mapIcon = Leaflet.icon({
iconUrl: mapMarkerImg,
iconSize:[58, 68],
iconAnchor:[29, 68],
popupAnchor:[170,2]
})
and then I can apply it on Marker component:
<Marker icon={mapIcon} position={[-22.9094183, -43.1850019]}>
<Popup
closeButton={false}
minWidth={240}
maxWidth={240}
className={"map-popup"}
>
Lar das meninas
<Link to="/orphanages/1">
<FiArrowRight size={20} color="#FFF" />
</Link>
</Popup>
</Marker>

Categories

Resources