Search code examples
javascriptreactjsleafletreact-leaflet

How to run react-leaflet hook only when button is clicked


I am trying to implement some simple movement controls on a react-leaflet map to allow "flying" to a set of predefined coordinates after pressing a button.

I'm aware that the Map instance created by react-leaflet can be accessed by child components and so I'm utilising the useMap() hook in a child component to achieve this as follows in NavigateToMarker.jsx:

import { useMap } from "react-leaflet";

export default function NavigateToMarker({position}) {
    const map = useMap();
    map.flyTo(position, 13)
    return null
}

MainMap.jsx looks like this:

import NavigateToMarker from './NavigateToMarker';

export default function MainMap() {
  const [position, setPosition] = React.useState([51.607642, -0.129303])

  const handleClick = (position) => {
    setPosition(position)
  }

  <MapContainer>
    <TileLayer />
    <GeoJSON data={geoJsonData} />
    <NavigateToMarker position={position} />
  </MapContainer>
  <Button onClick={() => handleClick([52.309611, -0.139303])}>Fly To Position</Button>
}

The button does fly to the specified position but my problem is that the NavigateToMarker code is ran every time the MainMap component renders.

I've tried including the button inside NavigateToMarker but because this component is then placed within a MapContainer component, the buttons don't show and I'm not sure how easily I'd be able to apply an CSS gridding and styling with this method.

Is there a way to ensure that NavigateToMarker is only invoked when the button in MainMap.jsx is clicked?


Solution

  • As suggested by @EzioMercer, wrapping NavigateToMarker in the memo function yielded the desired behavhaviour.

    //NavigateToMarker.jsx
    
    import { useMap } from "react-leaflet";
    import { memo } from 'react';
    
    export default memo(function NavigateToMarker({position, clicked}) {
        const map = useMap();
        if(clicked) map.setView(position, 13)
        return null
    })
    

    To prevent NavigateToMarker running on initial render, I added a boolean prop that's only set to true after the onClick() handler on the button is invoked.

    //MainMap.jsx
    
    import NavigateToMarker from './NavigateToMarker';
    
    export default function MainMap() {
      const [position, setPosition] = React.useState([51.607642, -0.129303])
      const [clicked, setClicked] = React.useState(false)
    
      const handleClick = (position) => {
        setClicked(true)
        setPosition(position)
      }
    
      <MapContainer>
        <TileLayer />
        <GeoJSON data={geoJsonData} />
        <NavigateToMarker position={position} clicked={clicked} />
      </MapContainer>
      <Button onClick={() => handleClick([52.309611, -0.139303])}>Fly To Position</Button>
    }