1

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

c-mcb92
  • 170
  • 1
  • 9
  • Your approach is not very 'react-ey'. Reacts main job is to re-render HTML quickly. Leaflet can render HTML as well using `DomUtil` but it's not designed to repeatedly re-render it. Can you just code your controls in HTML without using L.control? – teddybeard Oct 12 '21 at 14:52
  • 100% valid it's not very react because it's directly manipulating the DOM outside of React. I get that. The reason I liked it being in Leaflet's "control" structure is to take advantage of that styling for positioning on the map rather than using other CSS absolute positioning on the screen. I've done that with a different react component but wanted this to integrate with the map. Might not be able to anymore with v3 – c-mcb92 Oct 13 '21 at 16:29

1 Answers1

8

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
c-mcb92
  • 170
  • 1
  • 9