Search code examples
reactjsreact-reduxleafletreact-hooksreact-leaflet

How to get map properties and handle events in leaflet v3 with react + redux?


I am new at react redux (and hooks) and I've been following this general tutorial:

I'm also interested in using react-leaflet, which at the time of this writing is on v3 which uses hooks. I've gotten as far as being able to load data containing an array of latlng to generate markers:

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { Map, MapContainer, Marker, Popup, TileLayer, useMap } from "react-leaflet";
import { Icon } from "leaflet";
import "../../css/app.css";
import { useSelector, useDispatch } from "react-redux";
import { getMarkers, selectMarkers } from "../features/markerSlice";

export const LeafMap = () => {
    //marker state
    const stateMarker = useSelector(state => state.marker);
    const dispatch = useDispatch();
    useEffect(() => {dispatch(getMarkers());}, [dispatch]);

    // map state
    const stateMap = useSelector(state => state.map)

    if (stateMarker.markers.length > 0) {
        return (
            <MapContainer center={stateMap.center} zoom={stateMap.zoom} scrollWheelZoom={true}>
              <TileLayer
                attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              />
              {stateMarker.markers.map(el => (
                <Marker position={[el.latitude, el.longitude]}/>
              ))}
            </MapContainer>
        );
    } else {
        return (
            <MapContainer center={stateMap.center} zoom={stateMap.zoom} scrollWheelZoom={true}>
              <TileLayer
                attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              />
            </MapContainer>
        );
    }

}

At the moment, I'm pretty confused on how to handle click events (marker clicks) and also access the map state (current bounding box, current zoom level, etc). I'm getting errors such as I can't access the map state unless it's in a child component, or onclick handlers are not recognized, etc.

I want to eventually be able to do things such as load markers dynamically depend on zoom and bounding box, and click on a marker to zoom into it. This is probably due to me being an absolute beginning in react redux and hooks in general.

I'm not looking for anyone to just write the code for me, but if anyone can provide general guidance on how to accomplish these that would be super appreciated!


Solution

  • To handle marker click events use eventHandlers prop and listen to click event inside on Markers comp like this:

    const CustomMarkers = () => {
        const map = useMap();
        return markers.map((el, i) => (
          <Marker
            key={i}
            position={[el.latitude, el.longitude]}
            icon={icon}
            eventHandlers={{
              click: () => {
                console.log("marker clicked", el);
                console.log(map.getZoom());
              }
            }}
          />
        ));
      };
    

    You can derive each marker element like this and take also information regarding map using useMap hook when you click on each marker.

    To load markers dynamically depending on zoom level. An example could be this: You have a btn. When you click it you add dynamically a marker which has an event listener to zoom the map further. This will be triggered only if current map zoom level is 6 for example.

     function AddMarkerOnClick({ map }) {
            const onClick = () => {
              console.log(map.getZoom());
              if (map?.getZoom() === 6) {
                L.marker([39.77, -106.23], { icon })
                  .addTo(map)
                  .addEventListener("click", () => map.setZoom(4));
              }
            };
        
            return <button onClick={onClick}>Add marker on click</button>;
          }
    
    ...
    return (
    <>
          <AddMarkerOnClick map={map} />
          <MapContainer
            center={position}
          ...
    </>
    )
    

    To be able to get the map reference you need your custom comps to be under MapContainer as children or get the map instance using whenCreated prop of MapContainer and then pass it on your custom comp.

    Example Demo