const IconGen = ({ incomingData }) => {
const [dataMap, setDataMap] = useState({});
const dataMapTemp = {};
incomingData.BandData.forEach((each) => {
dataMapTemp[each.AntennaX].list.push(each);
dataMapTemp[each.AntennaX].angle= each.angle;
dataMapTemp[each.AntennaX].sId = each.SID;
});
if (!dataMap[1]) {
setDataMap(dataMapTemp);
}
return (
<div class="container_Sector" >
<div class="circle" style={{backgroundColor : "#c31605"></div>
<div
id = {dataMap && dataMap[1]?.sId }
style={{
rotate: `${dataMap[1]?.angle}deg`,
}}
onClick={() => {
alert("You clicked the sector!");
}
}
>
{dataMap[1]?.list.map((each) => generateSvg(each))}
</div>
<div
style={{
rotate: `${dataMap[2]?.angle}deg`,
}}
>
{dataMap[2]?.list.map((each) => generateSvg(each))}
</div>
<div
style={{
rotate: `${dataMap[3]?.angle}deg`,
}}
>
{dataMap[3]?.list.map((each) => generateSvg(each))}
</div>
</div>
);
};
export default IconGen;
//Parent Component
<MapContainer>
<Marker
key={data.SiteID}
position={[data.Latitude, data.Longitude]}
icon = <IconGen
incomingData={data}
/>
>
</Marker>
</Mapcontainer>
I am able to render custom icon using icon={L.divIcon({ className: "custom icon", html: ReactDOMServer.renderToString( <MyComponent/> ) })}
.
However the onClick
within the custom icon component does not trigger. onClick
is not working due to rendering the MyComponent using ReactDOMServer.renderToString
.
I need the onClick
event inside the custom component to function correctly.
I have now published this solution as a library at @adamscybot/react-leaflet-component-marker.
The reason that the onClick
handler does not work is that renderToString
means the component isn't truly mounted, in the sense that React is not aware of it as an ongoing concern. Running renderToString
reduces the component to whatever the DOM looks like for that component on its first render, and nothing else will change that from the point renderToString
is called.
The base problem here is that the react-leaflet
library doesn't support this out of the box. However, we could get around this by:
L.divIcon
to render a dummy div as the icon containing nothing. We will assign a unique ID for that div for each marker, which will come in handy later.Marker
components add
event to detect when the icon has actually been rendered to the DOM.We can encapsulate this behaviour in an EnhancedMarker
component, for ease of use.
Here is a working CodeSandbox of the proof of concept. In this proof of concept, I am rendering two buttons as a marker, each with click events that work.
The below includes the generic code that can be applied to any situation:
import React, { useState, useId, useMemo } from "react";
import { createPortal } from "react-dom";
import { MapContainer, TileLayer, Marker } from "react-leaflet";
// `EnhancedMarker` has the same API as `Marker`, apart from the `icon` can be a React component.
const EnhancedMarker = ({
eventHandlers,
icon: providedIcon,
...otherProps
}) => {
const [markerRendered, setMarkerRendered] = useState(false);
const id = "marker-" + useId();
const icon = useMemo(
() =>
L.divIcon({
html: `<div id="${id}"></div>`,
}),
[id]
);
return (
<>
<Marker
{...otherProps}
eventHandlers={{
...eventHandlers,
add: (...args) => {
setMarkerRendered(true);
if (eventHandlers?.add) eventHandlers.add(...args);
},
remove: (...args) => {
setMarkerRendered(false);
if (eventHandlers?.remove) eventHandlers.remove(...args);
},
}}
icon={icon}
/>
{markerRendered &&
createPortal(providedIcon, document.getElementById(id))}
</>
);
};
const MarkerIconExample = () => {
return (
<>
<button onClick={() => console.log("button 1 clicked")}>Button 1</button>
<button onClick={() => console.log("button 2 clicked")}>Button 2</button>
</>
);
};
const CENTER = [51.505, -0.091];
const ZOOM = 13;
const App = () => {
return (
<MapContainer center={CENTER} zoom={ZOOM}>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
/>
<EnhancedMarker position={CENTER} icon={<MarkerIconExample />} />
</MapContainer>
);
};
For your example, you should be able to:
EnhancedMarker
component.<Marker>
in your use case to <EnhancedMarker>
.<IconGen />
in the <EnhancedMarker>
icon
prop.