Leaflet geoman "Button with this name already exists" error when creating a new button in react

Posted by Zora Stowers on Monday, May 20, 2024

I am trying to create a custom button for arrows in the drawing tool of leaflet-geoman. The idea was to work with the copyDrawControl function, and to use Line as a model to make Polylines with arrow tips.

I wrote a code mostly inspired from this demonstration https://codesandbox.io/s/394eq?file=/src/index.js and modified it for my goals. Here is the code :

import { useEffect } from "react"; import { useLeafletContext } from "@react-leaflet/core"; import "@geoman-io/leaflet-geoman-free"; import "@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css"; const Geoman = () => { const context = useLeafletContext(); useEffect(() => { const leafletContainer = context.layerContainer || context.map; leafletContainer.pm.setGlobalOptions({ pmIgnore: false }); //draw control options leafletContainer.pm.addControls({ positions: { draw: 'topleft', edit: 'topright', }, drawMarker: false, rotateMode: false, cutPolygon: false, position: "bottomright" }); //new button leafletContainer.pm.Toolbar.copyDrawControl('Line', { name: 'SoonToBeArrow', block: 'draw', title: 'Display text on hover button', actions: [ // uses the default 'cancel' action 'cancel', ], }); return () => { leafletContainer.pm.removeControls(); leafletContainer.pm.setGlobalOptions({ pmIgnore: true }); }; }, [context]); return null; }; export default Geoman; 

When trying to add the copyDrawControl, I faced a bug that would announce that "Button with this name already exists"

I suspect its because I add the button inside a useEffect that gets called several times, but it's also the only way to access leafletContainer, since it must be updated everytime the context changes. I tried creating another useEffect that contains the same context and my new button, but it did not work.

Does anyone have any suggestion on how to solve this ?

Thnak you in advance

2 Answers

You only want to run this effect once, just after context becomes available. In order to do this, we can make a state variable to track whether or not you've already added the control:

const Geoman = () => { const context = useLeafletContext(); const [added, setAdded] = useState(false); useEffect(() => { const leafletContainer = context.layerContainer || context.map; // if the context is ready, and we've not yet added the control if (leafletContainer && !added){ leafletContainer.pm.setGlobalOptions({ pmIgnore: false }); //draw control options leafletContainer.pm.addControls({ // ... }); //new button leafletContainer.pm.Toolbar.copyDrawControl('Line', { // ... }); // register that we've already added the control setAdded(true); } return () => { leafletContainer.pm.removeControls(); leafletContainer.pm.setGlobalOptions({ pmIgnore: true }); }; }, [context]); return null; }; 

In this way, you effect will run whenever context changes - once context is ready, you add the control. You register that you've added the control, but then your if statement will make sure that further changes in context will not try to keep adding controls again and again.

1

BTW, a second option to using leaflet geoman with react leaflet is to use the official createControlComponent hook to create custom controls. This is not at all straightforward with leaflet-geoman, as createControlComponent requires you to feed it an instance of an L.Control that has all the required hooks and initializer methods. geoman does not have these - it is quite different in the way it initializes and adds to a map. However, you can create an L.Control from geoman methods, and then feed it to createControlComponent.

Create the L.Control:

/** * Class abstraction wrapper around geoman, so that we can create an instance * that is an extension of L.Control, so that react-leaflet can call all * L.PM methods using the expected L.Control lifecycle event handlers */ const GeomanControl = L.Control.extend({ initialize(options: Props) { L.PM.setOptIn(options.optIn ?? false); L.setOptions(this, options); }, addTo(map: L.Map) { const { globalOptions, events } = this.options; // This should never happen, but its better than crashing the page if (!map.pm) return; map.pm.addControls(toolbarOptions); map.pm.setGlobalOptions({ pmIgnore: false, ...globalOptions, }); // draw control options map.pm.addControls({ // ... }); // new button map.pm.Toolbar.copyDrawControl('Line', { // ... }); Object.entries(events ?? {}).forEach(([eventName, handler]) => { map.on(eventName, handler); }); }, }); 

Then simply use createControlComponent

const createControl = (props) => { return new GeomanControl(props); }; export const Geoman = createControlComponent(createControl); 

You can add quite a lot of logic into the addTo method, and base a lot of its behaviors off the props you feed to <Geoman />. This is another flexible way of adapting geoman for react-leaflet v4.

ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJobmxvY2x%2BdIOOpZyanpyawW6zxKikmqZdl8K1wM6nZLChpJ16tbTIrGSnmZ2aeqK40Z6YnbFdmsWqv9OsZJ6qoqS%2FbsPHnqVmm6KarrW1zaBkmmWemsQ%3D