javascriptreactjsleafletreact-leafletreact-leaflet-v3

React-Leaflet LocateControl component - keeps duplicating on each refresh


I'm using Locate Control in React Leaflet, but the Locate Control buttons are always duplicated, and sometimes I get 3 or 4 of them (see image below). I'm running the function through a useEffect with empty dependency to only fire it once, but no matter. I can target the class with display: none, but then both disappear. I feel like this might be an issue with Locate Control library? Really not sure. Open to any help or ideas.

import { useEffect } from "react"
import { useMap } from "react-leaflet"
import Locate from "leaflet.locatecontrol"
import "leaflet.locatecontrol/dist/L.Control.Locate.min.css"

const AddLocate = () => {
  const map = useMap()

  useEffect(() => {
    const locateOptions = {
      position: "bottomleft",
      flyTo: true,
    }
    const locateControl = new Locate(locateOptions)
    locateControl.addTo(map)
  }, [])

  return null
}

export default AddLocate;

Duplicated locate control


Solution

  • Looks like you use a package made for leaflet. Which should for the most parts be okay. However the way you add the control is not really the react-leaflet way, where we want to add add components rather than add "stuff" directly to the map.

    Below you can see how easy it is to implement a location component that you simply just can add as component within your MapContainer.

    import { ActionIcon } from "@mantine/core";
    import React, { useState } from "react";
    import { useMapEvents } from "react-leaflet";
    import { CurrentLocation } from "tabler-icons-react";
    import LeafletControl from "./LeafletControl";
    
    interface LeafletMyPositionProps {
      zoom?: number;
    }
    
    const LeafletMyPosition: React.FC<LeafletMyPositionProps> = ({ zoom = 17 }) => {
      const [loading, setLoading] = useState<boolean>(false);
      const map = useMapEvents({
        locationfound(e) {
          map.flyTo(e.latlng, zoom);
          setLoading(false);
        },
      });
    
      return (
        <LeafletControl position={"bottomright"}>
          <ActionIcon
            onClick={() => {
              setLoading(true);
              map.locate();
            }}
            loading={loading}
            variant={"transparent"}
          >
            <CurrentLocation />
          </ActionIcon>
        </LeafletControl>
      );
    };
    
    export default LeafletMyPosition;
    

    And for LeafletControl I just have this reusable component:

    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;
    }
    
    const LeafletControl: React.FC<LeafLetControlProps> = ({
      position,
      children,
    }) => {
      const divRef = useRef(null);
    
      useEffect(() => {
        if (divRef.current) {
          L.DomEvent.disableClickPropagation(divRef.current);
          L.DomEvent.disableScrollPropagation(divRef.current);
        }
      });
    
      return (
        <div ref={divRef} className={position && ControlClasses[position]}>
          <div className={"leaflet-control"}>{children}</div>
        </div>
      );
    };
    
    export default LeafletControl;