As the title says, I’m searching how to create a button to enable/disable zoom and pan for leaflet in javascript react. Actually creating a button is fine, but getting the effect I want on my map is not.
I am working on a react project and trying to implement a leaflet map that swiches between an “edit” mode and a “static” mode. Edit would display the map with leaflet-draw commands and allow the user to zoom and pan. Static would hide the whole draw interface and disallow zoom and pan, practically turning the map to a fix image.
I already implemented a leaflet map and a button that adds/remove a “hidden” class to the draw object in my component. Now I am trying to add something to the same button that would switch the zoom and pan between active and inactive.
I manually changed the properties “scrollWheelZoom” and “dragging” in my code and obtained the desired effects, so I guess I now need to change them with the button. I tried to use a querySelector to select my map and change the properties, but it didn’t work (I guess the map is already rendered and is not going to change). Then I tried to set the parameter values equal to the boolean state that I use as my switch for the button, but I obtained the same results. Most exemples I saw on the web use a completely different way of creating the map, if I understand well that’s because they were not using react in addition to leaflet.
Is there a way to update these properties of my map or to create the map in another way that would allow me to update the properies more easily ?
Here is the current version of the code. “scrollWheelZoom” is updated with the query method and “dragging” with the state method.
//css imports import './Map.css'; import 'leaflet/dist/leaflet.css'; import 'leaflet-draw/dist/leaflet.draw.css'; //library imports import React, { useRef, useEffect, useState } from 'react'; import { MapContainer, TileLayer, FeatureGroup } from 'react-leaflet'; import { EditControl } from 'react-leaflet-draw' import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import L from 'leaflet'; //solution to the missing icon phenomenon in draw delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'), iconUrl: require('leaflet/dist/images/marker-icon.png'), shadowUrl: require('leaflet/dist/images/marker-shadow.png') }); //component proper export default function Map(props) { const [map, setMap] = useState(null) //state that will contain the map object let [editState, setEditState] = useState(true); // state to toggle the edit buttons //draw object that contains parameters for the drawing functions let drawObject = useRef({ circlemarker: false }); //handler for the edit button to hide/display the drawing tools function editHandler(e) { const $plc = e.target.closest('.placeholders');//select placeholder const $editObjectDraw = $plc.querySelector('.leaflet-draw');//select target object const $editObjectMap = $plc.querySelector('.mapContainer'); if (editState) { $editObjectDraw.classList.add('visually-hidden');//update visual state $editObjectMap.scrollWheelZoom = editState; } else { $editObjectDraw.classList.remove('visually-hidden');//update visual state $editObjectMap.scrollWheelZoom = editState; } setEditState(!editState); //reverse boolean state to go from add to remove } return ( <div> <MapContainer className="mapContainer" center={[45.5053, -73.6138]} zoom={1} scrollWheelZoom={true} dragging={editState} minZoom={0} maxZoom={18} ref={setMap}> <TileLayer attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> <FeatureGroup> <EditControl className='drawControl' position='topright' onEdited={_onChange} onCreated={_onChange} onDeleted={_onChange} onMounted={_onChange} onEditStart={_onChange} onEditStop={_onChange} onDeleteStart={_onChange} onDeleteStop={_onChange} draw={drawObject.current} /> </FeatureGroup> </MapContainer> <button className="mapControl" onClick={editHandler}> <EditOutlinedIcon style={{ fill: "#FCF0E5" }} /> <span>Edit</span> </button> </div> ) }
This is my first question here, I hope I didn’t make any big mistake before posting it. Thank you in advance for the help.
Advertisement
Answer
As I wrote in the comments you should not use vanilla js in combination with react as both are responsible for changing the state and that will result to unexpected bugs and behavior.
So what you need to achieve the goal is to initially disable all map interactions and then enabling them as you probably thought. In the beginning you can easily achieve that because you pass the properties as props and you create a state variable to toggle between edit buttons display. Only exception is the zoom control that needs to be disabled via a useEffect
in the first place. But in order to dynamically enable the map interactions you need to do it via the map reference because the mapcontainer's
props are immutable meaning you cannot change them via state. Once they created they cannot be changed. You have two options to do that. Change them directly in the button onClick
event or create a custom react-leaflet component that will be rendered when edit mode is enabled or true.
export default function Map() { const [map, setMap] = useState(null); //state that will contain the map object let [editMode, setEditMode] = useState(false); // state to toggle the edit buttons useEffect(() => { if (map) map.zoomControl.disable(); }, [map]); const handleClick = () => { if (!editMode) { map.touchZoom.enable(); map.doubleClickZoom.enable(); map.scrollWheelZoom.enable(); map.keyboard.enable(); map.zoomControl.enable(); map.dragging.enable(); } else { map.touchZoom.disable(); map.doubleClickZoom.disable(); map.scrollWheelZoom.disable(); map.keyboard.disable(); map.zoomControl.disable(); map.dragging.disable(); } setEditMode((prevState) => !prevState); }; <MapContainer className="mapContainer" center={[45.5053, -73.6138]} zoom={1} style={{ height: "90vh" }} minZoom={0} maxZoom={18} zoomSnap={false} ref={setMap} scrollWheelZoom={false} dragging={false}> ... {editMode && ( <FeatureGroup> <EditControl onEdited={() => {}} onCreated={() => {}} onDeleted={() => {}} draw={{ rectangle: false }} /> </FeatureGroup> )} </MapContainer> <button className="mapControl" onClick={handleClick}> <EditOutlinedIcon /> <span>{!editMode ? "Static" : "Edit"} mode</span> </button> </>
You can simplify the onClick
function even more. Check the demo