BlueBeret
BlueBeret

Reputation: 607

Use leaflet map object outside useEffect in react

I have a button that will edit the mymap object (for example add marker or circle). But it give me error that says "mymap is not defined". I have tried declare the mymap var outside the useEffect but it doesn't work because useEffect doesn't save the assignment. I also tried useState but it doesn't work either.

here's my code

import 'leaflet/dist/leaflet.css'
import L from 'leaflet/dist/leaflet'
import { useEffect, useState} from 'react'

import './css/grid.css'

export const Grid = ({mapID}) => {
    const centerCoor = //redacted
    var layerGroup = L.layerGroup()

    function addGrid(){
        var btnel = document.getElementById("btn-grid");
        btnel.classList.toggle("btn-active");

        if (btnel.classList.contains("btn-active")){
            // do something with "mymap"
            mymap.on('click', (e)=>{
            })
        }
    }

    useEffect(()=>{
        var mymap = L.map(mapID, {
            center: centerCoor,
            zoom:18
        });

        L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
            attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
            maxZoom: 18,
            id: 'mapbox/satellite-v9',
            tileSize: 512,
            zoomOffset: -1,
            accessToken: //redacted
            }
        ).addTo(mymap);


        

        return () => {
            mymap.off()
            mymap.remove()
        }
    },[])


    return (
        <div>
            <button onClick={addGrid} className="" id="btn-grid">ADD GRID</button>
            <div id={mapID}>
            </div>
        </div>
    )
}

Upvotes: 0

Views: 2777

Answers (1)

jharris711
jharris711

Reputation: 612

Leaflet keeps track of its state separately from React, which is why it's best to use the React-Leaflet binding. If you are going to use vanilla Leaflet inside of React, you should consider setting up a useEffect hook that handles creating the map instance and setting the map instance to state. Then you can access the map instance to add event listeners, add markers/popups, etc. This also helps React keep track of Leaflet's current state and monitor for any changes. Here is an example component:

import React, { useEffect, useRef, useState } from 'react';
import L from 'leaflet';

const MapComponent = (props) => {
  // Map state:
  const [mapInstance, setMapInstance] = useState(null);
  const [marker, setMarker] = useState(null);

  // Map refs:
  const mapRef = useRef(null);
  const tileRef = useRef(null);
  const markerRef = useRef(null);

  // Base tile for the map:
  tileRef.current = L.tileLayer(
    `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`,
    {
      attribution:
        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
    }
  );

  const mapStyles = {
    overflow: 'hidden',
    width: '100%',
    height: '100vh',
  };

  // Options for our map instance:
  const mapParams = {
    center: [37.0902, -95.7129], // USA
    zoom: 3,
    zoomControl: false,
    zoomSnap: 0.75,
    layers: [tileRef.current], // Start with just the base layer
  };

  // Map creation:
  useEffect(() => {
    mapRef.current = L.map('map', mapParams);
    // Add an event listener:
    mapRef.current.on('click', () => {
      alert('map clicked');
    });
    // Set map instance to state:
    setMapInstance(mapRef.current);
  }, []); // <- Empty dependency array, so it only runs once on the first render.

  // If you want to use the mapInstance in a useEffect hook,
  // you first have to make sure the map exists. Then, you can add your logic.
  useEffect(() => {
    // Check for the map instance before adding something (ie: another event listener).
    // If no map, return:
    if (!mapInstance) return;
    if (mapInstance) {
      mapInstance.on('zoomstart', () => {
        console.log('Zooming!!!');
      });
    }
  }, [mapInstance]);

  // Toggle marker on button click:
  const handleClick = () => {
    if (marker) {
      marker.removeFrom(mapInstance);
      markerRef.current = null;
    } else {
      markerRef.current = L.marker([40.7128, -74.006]).addTo(mapInstance);
    }
    setMarker(markerRef.current);
  };

  return (
    <>
      <button onClick={handleClick}>
        {`Click to ${marker ? 'remove' : 'add'} marker`}
      </button>
      <div id="map" style={mapStyles} />
    </>
  );
};

export default MapComponent;

Here is a live sandbox I've set up so you can test it out: LIVE DEMO

Upvotes: 4

Related Questions