I have a button that will edit the mymap object (for example add marker or circle). But it give me error that says "mymap is not defined". I have tried declare the mymap var outside the useEffect but it doesn't work because useEffect doesn't save the assignment. I also tried useState but it doesn't work either.
here's my code
import 'leaflet/dist/leaflet.css'
import L from 'leaflet/dist/leaflet'
import { useEffect, useState} from 'react'
import './css/grid.css'
export const Grid = ({mapID}) => {
const centerCoor = //redacted
var layerGroup = L.layerGroup()
function addGrid(){
var btnel = document.getElementById("btn-grid");
btnel.classList.toggle("btn-active");
if (btnel.classList.contains("btn-active")){
// do something with "mymap"
mymap.on('click', (e)=>{
})
}
}
useEffect(()=>{
var mymap = L.map(mapID, {
center: centerCoor,
zoom:18
});
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/satellite-v9',
tileSize: 512,
zoomOffset: -1,
accessToken: //redacted
}
).addTo(mymap);
return () => {
mymap.off()
mymap.remove()
}
},[])
return (
<div>
<button onClick={addGrid} className="" id="btn-grid">ADD GRID</button>
<div id={mapID}>
</div>
</div>
)
}
Leaflet keeps track of its state separately from React, which is why it's best to use the React-Leaflet binding. If you are going to use vanilla Leaflet inside of React, you should consider setting up a useEffect
hook that handles creating the map instance and setting the map instance to state. Then you can access the map instance to add event listeners, add markers/popups, etc. This also helps React keep track of Leaflet's current state and monitor for any changes. Here is an example component:
import React, { useEffect, useRef, useState } from 'react';
import L from 'leaflet';
const MapComponent = (props) => {
// Map state:
const [mapInstance, setMapInstance] = useState(null);
const [marker, setMarker] = useState(null);
// Map refs:
const mapRef = useRef(null);
const tileRef = useRef(null);
const markerRef = useRef(null);
// Base tile for the map:
tileRef.current = L.tileLayer(
`https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`,
{
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}
);
const mapStyles = {
overflow: 'hidden',
width: '100%',
height: '100vh',
};
// Options for our map instance:
const mapParams = {
center: [37.0902, -95.7129], // USA
zoom: 3,
zoomControl: false,
zoomSnap: 0.75,
layers: [tileRef.current], // Start with just the base layer
};
// Map creation:
useEffect(() => {
mapRef.current = L.map('map', mapParams);
// Add an event listener:
mapRef.current.on('click', () => {
alert('map clicked');
});
// Set map instance to state:
setMapInstance(mapRef.current);
}, []); // <- Empty dependency array, so it only runs once on the first render.
// If you want to use the mapInstance in a useEffect hook,
// you first have to make sure the map exists. Then, you can add your logic.
useEffect(() => {
// Check for the map instance before adding something (ie: another event listener).
// If no map, return:
if (!mapInstance) return;
if (mapInstance) {
mapInstance.on('zoomstart', () => {
console.log('Zooming!!!');
});
}
}, [mapInstance]);
// Toggle marker on button click:
const handleClick = () => {
if (marker) {
marker.removeFrom(mapInstance);
markerRef.current = null;
} else {
markerRef.current = L.marker([40.7128, -74.006]).addTo(mapInstance);
}
setMarker(markerRef.current);
};
return (
<>
<button onClick={handleClick}>
{`Click to ${marker ? 'remove' : 'add'} marker`}
</button>
<div id="map" style={mapStyles} />
</>
);
};
export default MapComponent;
Here is a live sandbox I've set up so you can test it out: LIVE DEMO