Skip to content
Advertisement

How to create a button to enable/disable zoom and pan for leaflet in javascript react?

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='&copy; <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

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement