Search code examples
reactjsleafletreact-leafletreact-leaflet-v3

React-Leaflet-v3 Reusable Custom Control


Background

For React Leaflet v2, there was an NPM plugin, react-leaflet-control, that allowed you to create any kind of control and place it into the react-leaflet control container. Obviously with the introduction of RL-v3, this no longer works in v3 with the API changes. I want to create a custom control wrapper to allow me to place in it any type of React Node.

Current status

The code that I have currently works...but doesn't. I pulled from the example in this Stack Overflow post: React Leaflet V3 Custom Control that gets me to the 99% solution of creating a custom control. However, my use case is a toolbar on the map with buttons that are interactable (colors to designate active tool). With this code, however, I have that functionality, but because every render causes a new control to be created, the Tooltip flickers as it is losing its anchor element.

Desired behavior

I want a toolbar that allows users to select tools to perform actions on the map (think old-school leaflet-draw. And to provide feedback, I want the button to change color when the tool is active and for UX, I want tooltips to describe the action of the button.

Actual behavior

The toolbar exists, users can select tools and there is UI feedback, however, the tooltips lose their anchor element as the control is removed and re-added on every render when selecting a tool.

Code Sandbox

https://codesandbox.io/s/react-leaflet-custom-control-n1xpv


Solution

  • I ended up with an answer that kind of takes in what @teddybeard was saying. If I just created my new div with the class as suggested, it would be placed on top of any default controls such as ZoomControl or ScaleControl. Instead, what I did was grab the actual position div container from the DOM, and then created a ReactDOM portal into that container and added my control in that way.

    It works, doesn't have the issues with visual flashing due to the React Effect removing and re-adding the control on every render and I still get the same positioning.

    It's live on npm and github at https://github.com/chris-m92/react-leaflet-custom-control and https://npmjs.com/package/react-leaflet-custom-control

    const POSITION_CLASSES = {
      bottomleft: 'leaflet-bottom leaflet-left',
      bottomright: 'leaflet-bottom leaflet-right',
      topleft: 'leaflet-top leaflet-left',
      topright: 'leaflet-top leaflet-right',
    }
    
    const Control = (props: Props): JSX.Element => {
      const [container, setContainer] = React.useState<any>(document.createElement('div'))
      const positionClass = (props.position && POSITION_CLASSES[props.position] || POSITION_CLASSES.topright)
    
      React.useEffect(() => {
        const targetDiv = document.getElementsByClassName(positionClass)
        setContainer(targetDiv[0])
      }, [])
    
      const controlContainer = (
        <div className='leaflet-control leaflet-bar'>{props.children}</div>
      )
    
      L.DomEvent.disableClickPropagation(container)
    
      return ReactDOM.createPortal(
        controlContainer,
        container
      )
    }
    
    export default Control