javascript - flyTo function not working with dynamic position - Stack Overflow

admin2025-04-18  4

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;
}
Share Improve this question edited Jan 29 at 9:34 DarkBee 15.5k8 gold badges72 silver badges118 bronze badges asked Jan 29 at 9:32 NorthbretNorthbret 32 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

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;
转载请注明原文地址:http://anycun.com/QandA/1744976848a90297.html