简体   繁体   中英

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>
    )
}

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, 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,
    maxBounds: L.latLngBounds(L.latLng(-150, -240), L.latLng(150, 240)),
    closePopupOnClick: false,
    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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM