2

I'm using react-leaflet to display a map with dynamic markers loaded from an API. Until now, I've been loading the Markers in the render, but I realised this was rendering unnecessarily re-rending the whole Leaflet Map each time anything changed (e.g. zoom, location). I think I can remove my zoom and location state and call native Leaflet functions for these, but ultimately I need to load my data after initial render, resulting a second unnecessary render in the very least. Is React really designed so that this is the preferred way?

function MyMap() {
  const [markers, setMarkers] = useState([]);

  useEffect(() => {
    console.log('useEffect');

    getMyData().then(a => {
      console.log('data load');
      setMarkers(a);
    });
  }, []);

  console.log('map render');
  return (
    <Map id='map' className='map' center={[52, 0]} zoom={6}>
      <TileLayer
        url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      />
      <LayerGroup>
        {markers.map(a => {
          return (
            <Marker
              key={a._id}
              position={[a.location.latitude, a.location.longitude]}
              icon={myIcon}
            />
          );
        })}
      </LayerGroup>
    </Map>
  );
}

Console output:

map render
useEffect
data load
map render

I was tempted to fix this by dynamically adding markers in the normal Leaflet way:

L.marker([50.5, 30.5]).addTo(map);

but this appears non-trivial in react-leaflet. In looking for how to dynamically add Markers I came across React Leaflet: Add markers dynamically, specifically the top answer suggesting that shouldn't be done. The associated jsfiddle re-renders the whole map for each click. Perhaps that's fine?

Mat
  • 82,161
  • 34
  • 89
  • 109

1 Answers1

1

To prevent map re-render once marker is added the following list of changes could be applied:

a) in a MyMap (parent) component in markers state keep track only a markers loaded from data source

Note: markers state is not getting updated once map is clicked in this component

function MyMap() {
  const [markers, setMarkers] = useState([]);

  useEffect(() => {
    console.log("useEffect");
    getMyData().then((data) => {
      console.log("data load");
      setMarkers(data);
    });
  }, []);

  console.log("map render");
  return (
    <Map id="map" className="map" center={[52, 0]} zoom={4}>
      <TileLayer
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      />
      <MyLayer defaultMarkers={markers} />
    </Map>
  );
}

b) introduce a separate (child) component with markers state to keep track of markers loaded from data source (set it with a default value passed via props) along with a markers added on map click event:

function MyLayer(props, ref) {
  const { defaultMarkers } = props;
  const [markers, setMarkers] = useState(defaultMarkers);
  const mapProps = useLeaflet();


  const addMarker = useCallback((e) => {
    const newMarker = {
      location: e.latlng,
    };
    console.log(newMarker);
    setMarkers((existingMarkers) => [...existingMarkers, newMarker]);
  }, []);


  useEffect(() => {
    setMarkers(defaultMarkers);  
    mapProps.map.on('click',addMarker)
  }, [addMarker,mapProps,defaultMarkers]);


  return (
    <LayerGroup>
      {markers.map((marker, idx) => {
        return (
          <Marker
            key={idx}
            position={[marker.location.lat, marker.location.lng]}
          />
        );
      })}
    </LayerGroup>
  );
}

Here is a demo

Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193