How to prevent event bubbling on child of React-Leaflet Map
Asked Answered
R

2

13

I have a React-Leaflet map which I am rendering a div inside.

For some reason, interacting with the contents of the div causes the map beneath to respond (eg: double-clicking will zoom the map, dragging will pan the map) - even when I'm calling e.stopPropagation() in handlers attached to the div.

As I understand it, calling stopPropagation() should prevent the DOM events from ever reaching the map itself.

Why does it appear that stopPropagation() is being ignored?

How can I render a div inside the map without it's events bubbling to the map itself?

Here is an example codepen showing the problem.

import { Map, TileLayer } from 'react-leaflet';

const MyMap = props => (
  <Map zoom={13} center={[51.505, -0.09]}>
    <TileLayer url={"http://{s}.tile.osm.org/{z}/{x}/{y}.png"} />

    {/* 
          HOW do I get this div to NOT pass it's events down to the map?!?! 
          Why does e.stopPropagation() appear to not work?
    */}
    <div 
      id="zone"
      onClick={e => e.stopPropagation()}
      onMouseDown={e => e.stopPropagation()}
      onMouseUp={e => e.stopPropagation()}
    >
      <p>Double-click or click and drag inside this square</p>
      <p>Why does the map zoom/pan?</p>
    </div>

  </Map>
);

ReactDOM.render(<MyMap />, document.getElementById('root'));
Real answered 12/7, 2019 at 1:45 Comment(0)
G
13

For Leaflet map use L.DomEvent.disableClickPropagation instead which:

Adds stopPropagation to the element's 'click', 'doubleclick', 'mousedown' and 'touchstart' events.

Example

function MyMap() {
  return (
    <div>
      <Map zoom={13} center={[51.505, -0.09]}>
        <TileLayer url={"http://{s}.tile.osm.org/{z}/{x}/{y}.png"} />
        <MyInfo />
      </Map>
    </div>
  );
}

where

function MyInfo() {
  const divRef = useRef(null);

  useEffect(() => {
    L.DomEvent.disableClickPropagation(divRef.current);
  });

  return (
    <div ref={divRef} id="zone">
      <p>Double-click or click and drag inside this square</p>
      <p>Why does the map zoom/pan?</p>
    </div>
  );
}

Updated codepen

Alternative option

Another option to stop div element from propagation to map events would be to place div outside of the map element:

<div>
  <Map zoom={13} center={[51.505, -0.09]}>
    <TileLayer url={"http://{s}.tile.osm.org/{z}/{x}/{y}.png"} />
  </Map>
  <MyInfo />
</div>

where

function MyInfo() {
  return (
    <div id="zone">
      <p>Double-click or click and drag inside this square</p>
      <p>Why does the map zoom/pan?</p>
    </div>
  );
}
Gramineous answered 12/7, 2019 at 19:30 Comment(6)
I would love to know why e.stopPropagation() on the div itself doesn't work tho. How does Leaflet receive these events if they're not passed up from the div?Real
The way how Leaflet register event listeners internally,some extra steps are performed to register click event handler for Leaflet controls (such as ZoomIn control). Registering stopPropagation for div element only does not prevent leaflet controls click event to fire (that's the reason why map gets zoom in/out)Gramineous
Error message occurs L is not definedBuffoon
@Tekson import L from 'leaflet';Nimble
I tried this approach. It works, but now the problem is I can't click on the select box(mui select) inside a div inside map.Camporee
The ref might be null inside the useEffect (therefore throwing errors like Cannot read properties of null (reading '_leaflet_events')). It's better to handle it while it's being assigned, like ref={(ref) => {if (!ref) return; L.DomEvents.disableClickPropagation(ref); }}. I've answered the question with my solution.Hipbone
H
0

For those using more recent versions of react-leaflet (e.g.: ^4.2.1), I could only make it work with react-leaflet-custom-control

{/* import Control from "react-leaflet-custom-control"; */}
<Control position="topright">
  <div
    ref={(ref) => {
      if (!ref) return;
      /** import L from "leaflet"; */
      L.DomEvent.disableClickPropagation(ref).disableScrollPropagation(ref);
    }}
  >
    <button onClick={() => console.log('Clicked')}>Click me</button>
  </div>
</Control>

Codesandbox

Hipbone answered 19/4, 2023 at 22:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.