I'm working on a react project, where I display some markers on a leaflet map. I added two buttons on my map. One of them should fly to the position of the selected marker, when I click it. Therefor I pass the position of the marker (implemented as a state variable in my main component) to the button component and use it in my onClick function.
Till then everything works, it also flies to my default position, when no marker is selected, but when I change the marker (and so the position) and click the button, it's not flying to the desired position.
import {useEffect} from "react";
import {createRoot} from "react-dom/client";
import {useMap} from "react-leaflet";
import L from "leaflet";
import locateIcon from '../assets/my-location.svg'
import routeIcon from '../assets/route.svg'
import '../components/IconButton.css'
export default function CustomControls({position}) {
// Add icons
const LocateControlIcon = () => <img src={locateIcon}/>;
const RouteControlIcon = () => <img src={routeIcon}/>;
const map = useMap();
// Functionality buttons
function handleLocateButton() {
console.log('Fly to: ' + position);
map.flyTo(position);
}
function handleRouteButton() {
alert('Button clicked!');
}
useEffect(() => {
const CustomControl = L.Control.extend({
position: "topright",
onAdd: () => {
const container = L.DomUtil.create('div', 'custom-control');
container.style.margin = '16px 16px 0 0';
const locateButton = L.DomUtil.create('button', 'icon-button', container);
locateButton.onclick = handleLocateButton;
const routeButton = L.DomUtil.create('button', 'icon-button', container);
routeButton.style.margin = '8px 0 0 0';
routeButton.onclick = handleRouteButton;
// Render icons in the buttons
createRoot(locateButton).render(<LocateControlIcon/>);
createRoot(routeButton).render(<RouteControlIcon/>);
L.DomEvent.disableClickPropagation(container);
return container;
},
});
const controlInstance = new CustomControl();
map.addControl(controlInstance);
return () => {
map.removeControl(controlInstance);
};
}, [map]);
return null;
}
I'm working on a react project, where I display some markers on a leaflet map. I added two buttons on my map. One of them should fly to the position of the selected marker, when I click it. Therefor I pass the position of the marker (implemented as a state variable in my main component) to the button component and use it in my onClick function.
Till then everything works, it also flies to my default position, when no marker is selected, but when I change the marker (and so the position) and click the button, it's not flying to the desired position.
import {useEffect} from "react";
import {createRoot} from "react-dom/client";
import {useMap} from "react-leaflet";
import L from "leaflet";
import locateIcon from '../assets/my-location.svg'
import routeIcon from '../assets/route.svg'
import '../components/IconButton.css'
export default function CustomControls({position}) {
// Add icons
const LocateControlIcon = () => <img src={locateIcon}/>;
const RouteControlIcon = () => <img src={routeIcon}/>;
const map = useMap();
// Functionality buttons
function handleLocateButton() {
console.log('Fly to: ' + position);
map.flyTo(position);
}
function handleRouteButton() {
alert('Button clicked!');
}
useEffect(() => {
const CustomControl = L.Control.extend({
position: "topright",
onAdd: () => {
const container = L.DomUtil.create('div', 'custom-control');
container.style.margin = '16px 16px 0 0';
const locateButton = L.DomUtil.create('button', 'icon-button', container);
locateButton.onclick = handleLocateButton;
const routeButton = L.DomUtil.create('button', 'icon-button', container);
routeButton.style.margin = '8px 0 0 0';
routeButton.onclick = handleRouteButton;
// Render icons in the buttons
createRoot(locateButton).render(<LocateControlIcon/>);
createRoot(routeButton).render(<RouteControlIcon/>);
L.DomEvent.disableClickPropagation(container);
return container;
},
});
const controlInstance = new CustomControl();
map.addControl(controlInstance);
return () => {
map.removeControl(controlInstance);
};
}, [map]);
return null;
}
I would guess that the buttons you add becomes static with the position fixed to the initial value. For most functions where I need to use useMap I create a component that returns null but that component can be placed inside the MapContainer which gives it access to the map context.
Here is my FlyTo component:
import React, { useEffect } from "react";
import { useMap } from "react-leaflet";
export interface FlyToProps {
latitude?: number;
longitude?: number;
zoom?: number;
onFlyToComplete?: () => void;
}
const FlyTo: React.FC<FlyToProps> = ({
latitude,
longitude,
zoom,
onFlyToComplete,
}) => {
const map = useMap();
useEffect(() => {
if (latitude && longitude) {
const handleMoveEnd = () => {
if (onFlyToComplete) {
onFlyToComplete();
}
map.off("moveend", handleMoveEnd);
};
map.on("moveend", handleMoveEnd);
map.flyTo([latitude, longitude], zoom ?? map.getZoom());
}
}, [latitude, longitude, map, zoom, onFlyToComplete]);
return null;
};
export default FlyTo;
You can then just pass the position to it.
{flyTo && (
<FlyTo
latitude={flyTo.lat}
longitude={flyTo.lng}
onFlyToComplete={() => {
clearFlyTo();
}}
/>
)}
On another note I would recommend you to just add you custom controls as regular react components you just have to use the correct classnames to place them accordingly. I also use a custom component class for this:
import L from "leaflet";
import React, { useEffect, useRef } from "react";
const ControlClasses = {
bottomleft: "leaflet-bottom leaflet-left",
bottomright: "leaflet-bottom leaflet-right",
topleft: "leaflet-top leaflet-left",
topright: "leaflet-top leaflet-right",
};
type ControlPosition = keyof typeof ControlClasses;
export interface LeafLetControlProps {
position?: ControlPosition;
children?: React.ReactNode;
offset?: [number, number];
}
const LeafletControl: React.FC<LeafLetControlProps> = ({
position,
children,
offset = [0, 0],
}) => {
const divRef = useRef(null);
useEffect(() => {
if (divRef.current) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
L.DomEvent.disableClickPropagation(divRef.current);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
L.DomEvent.disableScrollPropagation(divRef.current);
}
});
return (
<div
style={{
marginLeft: offset[0],
marginTop: offset[1],
}}
ref={divRef}
className={position && ControlClasses[position]}
>
<div className={"leaflet-control"}>{children}</div>
</div>
);
};
export default LeafletControl;