_onLocationFound doesn't work in leaflet.locationcontrol - javascript

I am using leaflet.locatecontrol to get users' location in the map but _onLocationFound doesn't work.
here is my map component
<Map
ref={ref}
center={initialState ? initialState.center : DEFAULT_CENTER}
zoom={initialState ? initialState.zoom : 5}
maxZoom={18}
onMove={(e) => {
onMapMove(e);
}}
>
<TileLayer
url={`${settings.getConfig().MAP_TILE_URL}${
settings.getConfig().MAP_X_API_KEY
}`}
attribution={settings.getConfig().MAP_ATTRIBUTION}
/>
{marker && (
<Marker
position={marker}
icon={L.icon({
iconUrl: "https://webstockreview.net/images/map-icon-png-6.png",
iconSize: [25, 30], // size of the icon
iconAnchor: [13, 36], // point of the icon which will correspond to marker's location
})}
/>
)}
<LocateControl options={locateOptions} />
</Map>
and this is the options I'm passing to LocateControl
position: "bottomright",
icon: "map-location-icon",
strings: {
title: "my location",
},
onLocationError: function (e) {
console.log(e);
console.log("Location access denied.");
},
_onLocationFound: function (e) {
console.log("event", e);
},
onActivate: () => {}, // callback before engine starts retrieving locations
}
and LocateControl is a simple component I implemented to separate the logic and here is its code:
import { withLeaflet } from "react-leaflet";
import Locate from "leaflet.locatecontrol";
class LocateControl extends Component {
componentDidMount() {
const { options, startDirectly } = this.props;
const { map } = this.props.leaflet;
const lc = new Locate(options);
lc.addTo(map);
if (startDirectly) {
// request location update and set location
lc.start();
}
}
render() {
return null;
}
}
export default withLeaflet(LocateControl);
my problem is that I want to save the location when it's found but _onLocationFound function won't be triggered even though the location is updated in the map and I can't find out what am I missing out because even in the documentation the function is simply past to locateControl
Here is the link to the source code of the package
I would be thankful if anyone could give me a hint

You're passing an option named _onLocationFound which is not on the list of supported options for Leaflet.LocateControl.
You probably did so because you looked at the code for Leaflet.LocateControl and confused the onLocationError option with the _onLocationError method, without noticing that the _onLocationError method explicitly calls its namesake option here...
_onLocationError: function(err) {
/* snip */
this.options.onLocationError(err, this);
},
...but you failed to realize that there is no onLocationFound option at all, and that the internal _onLocationFound method does not call any user-provided callback at all.
Instead, do it the documented way: Leverage the locationfound event on the map.

Related

Coordinates array from PolylineMeasure plugin (react leaflet)

I have 3 files:
1.
PolylineMeasure.jsx
import { MapControl, withLeaflet } from "react-leaflet";
import * as L from "leaflet";
class PolylineMeasure extends MapControl {
createLeafletElement() {
return L.control.polylineMeasure({
position: "topleft",
unit: "metres",
showBearings: true,
clearMeasurementsOnStop: false,
showClearControl: true,
showUnitControl: true,
});
}
componentDidMount() {
const { map } = this.props.leaflet;
const polylineMeasure = this.leafletElement;
polylineMeasure.addTo(map);
}
}
export default withLeaflet(PolylineMeasure);
Map.jsx
import { Map, TileLayer } from "react-leaflet";
import PolylineMeasure from "./PolylineMeasure";
import "leaflet/dist/leaflet.css";
import "leaflet/dist/leaflet.css";
import "leaflet.polylinemeasure/Leaflet.PolylineMeasure.css";
import "leaflet.polylinemeasure/Leaflet.PolylineMeasure";
const Leaflet = () => {
return (
<>
<Map
center={[52.11, 19.21]}
zoom={6}
scrollWheelZoom={true}
style={{ height: 600, width: "50%" }}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<PolylineMeasure />
</Map>
</>
);
};
export default Leaflet;
I'm using nextjs so I had to import without SSR.
home.js
import dynamic from "next/dynamic";
function HomePage() {
const Map = dynamic(() => import("../components/Map"), {
loading: () => <p>A map is loading</p>,
ssr: false,
});
return <Map />;
}
export default HomePage;
https://github.com/ppete2/Leaflet.PolylineMeasure
Using demos in link above, I was able to log an array of coorfinates like this:
{ ... }
polylineMeasure.addTo(map);
function debugevent() {
polylineMeasure._arrPolylines[0].arrowMarkers.map((el) => {
console.log(el._latlng);
});
}
map.on("polylinemeasure:toggle", debugevent);
How can I access these coordinates in nextjs (home.js file)?
How to render PolylineMeasure (Map.jsx file) already with coordinates by passing down an array as props?
So this is about 2 things: lifting up state, and capturing Leaflet.Polyline's internal events.
First, let's keep track of a state variable in Home.js, and pass its setter down into the map component:
function HomePage() {
const [pointarray, setPointarray] = useState()
const Map = dynamic(() => import("../components/Map"), {...})
return <Map setPointarray={setPointarray} />;
}
Now in Map, we need to get a reference to the underlying leaflet map so that we can attach some event handlers. You're using createLeafletElement and withLeaflet, so I assume you're using reat-leaflet version 2. (I recommend updating to v3 when you can).
const Leaflet = ({ setPointarray }) => {
const mapRef = React.useRef()
useEffect(() => {
if (mapRef && mapRef.current){
mapRef.current.leafletElement.on(
'polylinemeasure:finish',
currentLine => setPointarray(currentLine.getLatLngs())
)
}
}, [mapRef])
return (
<>
<Map
ref={mapRef}
...
>
<TileLayer ... />
<PolylineMeasure />
</Map>
</>
);
};
What happens here is that a ref is attached to your Map component, which references the underlying leaflet L.map instance. When that ref is ready, the code inside the useEffect if statement runs. It gets the map instance from mapRef.current.leafletElement, and attaches an event handler based on Leaflet.PolylineMeasure's events, specifically the event of when a drawing is complete. When that happens, it saves the drawn line to the state variable, which lives in the Home component.
There are a lot of variations on this, it just depends on what you're trying to do exactly. As far as feeding preexisting polyline coordinates down to PolylineMeasurer as props, I couldn't find any examples of that even with the vanilla leaflet PolylineMeasurer. I found a comment from the plugin author saying that "restoring of drawed measurements is not possible", which is essentially what we're talking about doing by passing props down to that component. I'm sure it can be done by digging into the source code and programmatically drawing a polyline, but I've run out of time, I'll try to revisit that later.
react-leaflet version 3 answer
As per request, here's how to do this with react-leaflet v3, while initializing the polylinemeasurer with data passed down as props.
Create custom react-leaflet v3 control
Creating custom components with react-leaflet is easier than ever. Take a look at createcontrolcomponent. If you're not used to reading these docs, it boils down to this: to create a custom control component, you need to make a function that returns the leaflet instance of the control you want to make. You feed that function to createcontrolcomponent, and that's it:
import { createControlComponent } from "#react-leaflet/core";
const createPolylineMeasurer = (props) => {
return L.control.polylineMeasure({ ...props });
};
const PolylineMeasurer = createControlComponent(createPolylineMeasurer);
export default PolylineMeasurer;
Altering the original plugin to seed data
However, in our case, we want to add some extra logic to pre-seed the PolylineMeasurer with some latlngs that we pass down as a prop. I put in a pull request to the original plugin to add a .seed method. However, in the case of react-leaflet, we need to be more careful than using the code I put there. A lot of the methods required to draw polylines are only available after the L.Control.PolylineMeasure has been added to the map. I spent probably way too much time trying to figure out where in the react/react-leaflet lifecyle to intercept the instance of the polylineMeasure after it had been added to the map, so my eventual solution was to alter the source code of Leaflet.PolylineMeasure.
In the onAdd method, after all the code has run, we add in this code, which says that if you use a seedData option, it will draw that seed data once the control is added to the map:
// inside L.Control.PolylineMeasure.onAdd:
onAdd: function(map) {
// ... all original Leaflet.PolylineMeasure code here ...
if (this.options.seedData) {
const { seedData } = this.options;
seedData.forEach((polyline) => {
// toggle draw state on:
this._toggleMeasure();
// start line with first point of each polyline
this._startLine(polyline[0]);
// add subsequent points:
polyline.forEach((point, ind) => {
const latLng = L.latLng(point);
this._mouseMove({ latLng });
this._currentLine.addPoint(latLng);
// on last point,
if (ind === polyline.length - 1) {
this._finishPolylinePath();
this._toggleMeasure();
}
});
});
}
return this._container;
}
This code programatically calls all the same events that would be called if a user turned on the control, clicked around, and drew their lines that way.
Tying it together
So now our <PolylineMeasurer /> component takes as its props the options that would be fed to L.control.polylineMeasure, in addition to a new optional prop called seedData which will cause the map to be rendered with that seedData:
const Map = () => {
return (
<MapContainer {...mapContainerProps}>
<TileLayer url={url} />
<PolylineMeasurer
position="topleft"
clearMeasurementsOnStop={false}
seedData={seedData}
/>
</MapContainer>
);
};
Working Codesandbox
Caveat
If by some other mechanism in your app the seedData changes, you cannot expect the PolylineMeasurer component to react in the same way that normal React components do. In create leaflet, this control is added to the map once with the options you feed it, and that's it. While some react-leaflet-v3 component factory functions come with an update paramter, createcontrolcomponent does not (i.e. its first argument is a function which creates a control instance, but it does not accept a second argument to potentially update the control instance like, say, createlayercomponent does).
That being said, you can apply a key prop to the PolylineMeasurer component, and if your seedData is changed somewhere else in the app, also change the key, and the PolylineMeasurer will be forced to rerender and draw your new data.

How do I get to the updated path of an editable PolyLine from react-google-maps/api?

I'm using the react-google-maps/api library, and I have an application where I need the user to edit a Polyline.
The problem I'm having is grabbing the path of the polyline after the user has finished editing.
If I use native react components, the path returned on the props from the polyline is the original path of the line - not the one edited by the user.
The code below is a cutdown version of where I'm trying to get the path of the line from the react component. If you try it and edit the line, the return array is the original path. I've seen some examples using the getPath() method, but I just can't seem to get this to work on the React component (ie polylineRef.current.getPath() returns a no function error.
How should I be getting the path information of the edited line?
import React, { Fragment, useRef } from "react";
import { GoogleMap, Polyline, useLoadScript } from "#react-google-maps/api";
const MapTest = (props) => {
const polylineRef = useRef();
const mapRef = useRef();
const mapContainerStyle = {
width: "80vw",
height: "80vh",
};
const showPath = () => {
console.log(polylineRef.current.props.path); //What should be here to show the edited path if its possible to access?
};
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.REACT_APP_GOOGLE_KEY,
});
const centre = { lat: 51.999889, lng: -0.98807 };
if (loadError) return "Error loading Google Map";
if (!isLoaded) return "Loading Maps....";
console.log(polylineRef.current.props.path);
return (
<Fragment>
<GoogleMap
mapContainerStyle={mapContainerStyle}
ref={mapRef}
zoom={10}
center={centre}
>
<Polyline
ref={polylineRef}
key={1}
path={[
{ lat: 51.9298274729133, lng: -1.0446431525421085 },
{ lat: 51.98483618577529, lng: -1.2423970587921085 },
]}
options={{ editable: true, strokeColor: "#ff0000" }}
/>
</GoogleMap>
<button
onClick={(event) => {
showPath(event);
}}
>
Show Path in Console
</button>
</Fragment>
);
};
export default MapTest;
If I use the native google API, then I can see the updated path, but I can't get a reference to the map created by the map to place the polyline onto.
If I can't access the edited path through the react component, how should I provide a reference to the google maps native API, so when I do
polyline = new google.maps.Polyline(//polyline options)
polyline.setMap(map) //Where do I get the handle for this map to put it on the map above?
/*I've tried using mapRef.current (not a map instance) and
mapRef.current.getInstance() - this makes the original map disappear, for reasons I don't understand*/
When I build this using the native API, I can access the edited path using the getPath() method, but I can't render this polyline on the component rendered above.
Other than building the map out of the native API I'm struggling to do this at the moment - but the benefits of the ease of rendering of React make me want to continue down this path for a while longer - is anyone able to help please?
I think this is what you are trying to achieve:
https://codesandbox.io/s/snowy-night-ony59?file=/src/App.js
My answer is based on:
https://codesandbox.io/s/reactgooglemapsapi-editing-a-polygon-popr2?file=/src/index.js:2601-2845
which I found by googling: react-google-maps-api editable polygon
Basically just copying and pasting the code referred by Daniele Cordano
import React, { useState, useRef, useCallback } from "react";
import ReactDOM from "react-dom";
import { LoadScript, GoogleMap, Polygon } from "#react-google-maps/api";
import "./styles.css";
// This example presents a way to handle editing a Polygon
// The objective is to get the new path on every editing event :
// - on dragging the whole Polygon
// - on moving one of the existing points (vertex)
// - on adding a new point by dragging an edge point (midway between two vertices)
// We achieve it by defining refs for the google maps API Polygon instances and listeners with `useRef`
// Then we bind those refs to the currents instances with the help of `onLoad`
// Then we get the new path value with the `onEdit` `useCallback` and pass it to `setPath`
// Finally we clean up the refs with `onUnmount`
function App() {
// Store Polygon path in state
const [path, setPath] = useState([
{ lat: 52.52549080781086, lng: 13.398118538856465 },
{ lat: 52.48578559055679, lng: 13.36653284549709 },
{ lat: 52.48871246221608, lng: 13.44618372440334 }
]);
// Define refs for Polygon instance and listeners
const polygonRef = useRef(null);
const listenersRef = useRef([]);
// Call setPath with new edited path
const onEdit = useCallback(() => {
if (polygonRef.current) {
const nextPath = polygonRef.current
.getPath()
.getArray()
.map(latLng => {
return { lat: latLng.lat(), lng: latLng.lng() };
});
setPath(nextPath);
}
}, [setPath]);
// Bind refs to current Polygon and listeners
const onLoad = useCallback(
polygon => {
polygonRef.current = polygon;
const path = polygon.getPath();
listenersRef.current.push(
path.addListener("set_at", onEdit),
path.addListener("insert_at", onEdit),
path.addListener("remove_at", onEdit)
);
},
[onEdit]
);
// Clean up refs
const onUnmount = useCallback(() => {
listenersRef.current.forEach(lis => lis.remove());
polygonRef.current = null;
}, []);
console.log("The path state is", path);
return (
<div className="App">
<LoadScript
id="script-loader"
googleMapsApiKey=""
language="en"
region="us"
>
<GoogleMap
mapContainerClassName="App-map"
center={{ lat: 52.52047739093263, lng: 13.36653284549709 }}
zoom={12}
version="weekly"
on
>
<Polygon
// Make the Polygon editable / draggable
editable
draggable
path={path}
// Event used when manipulating and adding points
onMouseUp={onEdit}
// Event used when dragging the whole Polygon
onDragEnd={onEdit}
onLoad={onLoad}
onUnmount={onUnmount}
/>
</GoogleMap>
</LoadScript>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For some reason, the useRef hook doesn't accept the function getPath/getPaths() with the typescript error : TS2339: Property 'getPath' does not exist on type 'MutableRefObject'
const handleNewPolygonPath = useCallback(() => {
const newPath = polygonRef.getPath();
dispatch(setNewPolygonPath(newPath));
console.log(newPolygonPath);
}, [dispatch, newPolygonPath]);

react leaflet marker with bage causes loop

I'm using react-Leaflet and I need multiple markers with badge but whenever I use onMoveEnd on my map and setState something, it causes loop and erros.
Also when I use onDragEnd instead of onMoveEnd the loop problem goes away but i can not drag anymore to where ever I want.
const onMapMoveEnd = (e) => {
setCenter(e.target.getCenter())
}
<Map ref={mapRef} onMoveend={(e) => onMapMoveEnd(e)}>
markers.map( (mark, index) => <MarkerWithBadge markerIcon={myIcon} position={position}>
{index}
</MarkerWithBadge>
)
const MarkerWithBadge = props => {
const initMarker = ref => {
if (ref) {
const popup = L.popup().setContent(props.children);
ref.leafletElement
.addTo(ref.contextValue.map)
.bindPopup(popup, {
className: "badge",
// closeOnClick: false,
autoClose: false
})
.openPopup()
// prevent badge from dissapearing onClick
.off("click");
}
};
return <Marker ref={initMarker} {...props} />;
};
export default MarkerWithBadge;
Try setting the autoPan option of L.popup() to false. It prevents the map from panning to fit the opened popup. This is what solved it for me at least.
Doc reference: https://leafletjs.com/reference-1.6.0.html#popup.

How to load the Google Places API in React [duplicate]

This question already has answers here:
How to implement Google Places API in React JS?
(2 answers)
Closed 2 years ago.
I want to use Google Maps and Places API in my React application for users to be able get hospitals in their area on the map.
I am using both #react-google-maps/api and use-places-autocomplete npm packages to interface with the API to display the map and places. I am using the useLoadScript hook to load in my API key and also declare libraries I will be using.
In this case, just as I have enabled Places and Maps JavaScript APIs, I supplied places in the libraries array.
I have tried adding the below script to the index.html file:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
but I get the error: You are loading the map multiple times.
Here's a snippet of my code where I use the useLoadScript hook from the #react-google-maps/api
Please how do I resolve this issue?
If you are using useLoadScript of the #react-google-maps/api library and use-places-autocomplete you need to supply the ['places'] as a value of your library to use the Places Autocomplete in your code. Please note that this useLoadScript will load the Google Maps JavaScript Script tag in your code and you don't need to add the script tag in the html file anymore.
From what I understand, your use case where the user will select a place from the autocomplete dropdown and the nearest hospital should be returned in the map.
To achieve this, you need to have this flow:
Use use-places-autocomplete to provide place suggestion when searching for a place. This library have a GetGeocode and a GetLatLng that you can use to get the coordinates of the chosen place.
Once you have the coordinates, you can use Places API Nearby Search, use the keyword hospital in your request and define a radius. This will search a list of hospital within the defined radius with the chosen place as the center.
You can loop through the list of result and pin each coordinates as markers in your map.
Here;s the sample code and a code snippet below. Make sure to add your API Key:
Index.js
import React from "react";
import { render } from "react-dom";
import Map from "./Map";
render(<Map />, document.getElementById("root"));
Map.js
/*global google*/
import React from "react";
import { GoogleMap, useLoadScript } from "#react-google-maps/api";
import Search from "./Search";
let service;
const libraries = ["places"];
const mapContainerStyle = {
height: "100vh",
width: "100vw"
};
const options = {
disableDefaultUI: true,
zoomControl: true
};
const center = {
lat: 43.6532,
lng: -79.3832
};
export default function App() {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: "YOUR_API_KEY",
libraries
});
const mapRef = React.useRef();
const onMapLoad = React.useCallback(map => {
mapRef.current = map;
}, []);
const panTo = React.useCallback(({ lat, lng }) => {
mapRef.current.panTo({ lat, lng });
mapRef.current.setZoom(12);
let map = mapRef.current;
let request = {
location: { lat, lng },
radius: "500",
type: ["hospital"]
};
service = new google.maps.places.PlacesService(mapRef.current);
service.nearbySearch(request, callback);
function callback(results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
for (let i = 0; i < results.length; i++) {
let place = results[i];
new google.maps.Marker({
position: place.geometry.location,
map
});
}
}
}
}, []);
return (
<div>
<Search panTo={panTo} />
<GoogleMap
id="map"
mapContainerStyle={mapContainerStyle}
zoom={8}
center={center}
options={options}
onLoad={onMapLoad}
/>
</div>
);
}
Search.js
import React from "react";
import usePlacesAutocomplete, {
getGeocode,
getLatLng
} from "use-places-autocomplete";
export default function Search({ panTo }) {
const {
ready,
value,
suggestions: { status, data },
setValue,
clearSuggestions
} = usePlacesAutocomplete({
requestOptions: {
/* Define search scope here */
},
debounce: 300
});
const handleInput = e => {
// Update the keyword of the input element
setValue(e.target.value);
};
const handleSelect = ({ description }) => () => {
// When user selects a place, we can replace the keyword without request data from API
// by setting the second parameter to "false"
setValue(description, false);
clearSuggestions();
// Get latitude and longitude via utility functions
getGeocode({ address: description })
.then(results => getLatLng(results[0]))
.then(({ lat, lng }) => {
panTo({ lat, lng });
})
.catch(error => {
console.log("😱 Error: ", error);
});
};
const renderSuggestions = () =>
data.map(suggestion => {
const {
place_id,
structured_formatting: { main_text, secondary_text }
} = suggestion;
return (
<li key={place_id} onClick={handleSelect(suggestion)}>
<strong>{main_text}</strong> <small>{secondary_text}</small>
</li>
);
});
return (
<div>
<input
value={value}
onChange={handleInput}
disabled={!ready}
placeholder="Where are you going?"
/>
{/* We can use the "status" to decide whether we should display the dropdown or not */}
{status === "OK" && <ul>{renderSuggestions()}</ul>}
</div>
);
}

Possible to render react component within mapboxgl.Popup() in .setHTML or .setDOMContent?

I am wondering if it is possible to render a react component within a mapboxgl.Popup(). Something like this:
componentDidMount() {
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(`<div>${<MapPopup />}<p>${moreText}</p></div>`)
//.setDOMContent(`${<MapPopup />}`) ?????
.addTo(this.props.mapboxMap)
})
Or should this be done using ReactDOM.render?
ReactDOM.render(<MapPopup />, document.getElementById('root'))
This project will have buttons and inputs in the popup that connect to a redux store.
Thanks for any input!
This works:
addPopup(el: JSX.Element, lat: number, lng: number) {
const placeholder = document.createElement('div');
ReactDOM.render(el, placeholder);
const marker = new MapboxGl.Popup()
.setDOMContent(placeholder)
.setLngLat({lng: lng, lat: lat})
.addTo(map);
}
(Where I've used typescript to illustrate types, but you can just leave these out for pure js.) Use it as
addPopup(<h1>Losers of 1966 World Cup</h1>, 52.5, 13.4);
You can try to implement React component:
export const Popup = ({ children, latitude, longitude, ...mapboxPopupProps }) => {
// this is a mapbox map instance, you can pass it via props
const { map } = useContext(MapboxContext);
const popupRef = useRef();
useEffect(() => {
const popup = new MapboxPopup(mapboxPopupProps)
.setLngLat([longitude, latitude])
.setDOMContent(popupRef.current)
.addTo(map);
return popup.remove;
}, [children, mapboxPopupProps, longitude, latitude]);
return (
/**
* This component has to have 2 divs.
* Because if you remove outter div, React has some difficulties
* with unmounting this component.
* Also `display: none` is solving that map does not jump when hovering
* ¯\_(ツ)_/¯
*/
<div style={{ display: 'none' }}>
<div ref={popupRef}>
{children}
</div>
</div>
);
};
After some testing, I have realized that Popup component was not rendering properly on the map. And also unmounting the component was unsuccessful. That is why there are two divs in return. However, it may happen only in my environment.
See https://docs.mapbox.com/mapbox-gl-js/api/#popup for additional mapboxPopupProps
useEffect dependencies make sure that MapboxPopup gets re-created every time something of that list changes & cleaning up the previous popup instance with return popup.remove;
I've been battling with this as well. One solution I found was using ReactDOM.render(). I created an empty popup then use the container generated by mapboxgl to render my React component.
marker.setPopup(new mapboxgl.Popup({ offset: 18 }).setHTML(''));
markerEl.addEventListener('mouseenter', () => {
markerEl.classList.add('enlarge');
if (!marker.getPopup().isOpen()) {
marker.getPopup().addTo(this.getMap());
ReactDOM.render(
component,
document.querySelector('.mapboxgl-popup-content')
);
}
});
const mapCardNode = document.createElement("div");
mapCardNode.className = "css-class-name";
ReactDOM.render(
<YourReactPopupComponent / > ,
mapCardNode
);
//if you have a popup then we remove it from the map
if (popupMarker.current) popupMarker.current.remove();
popupBox.current = new mapboxgl.Popup({
closeOnClick: false,
anchor: "center",
maxWidth: "240px",
})
.setLngLat(coordinates)
.setDOMContent(mapCardNode)
.addTo(map);
I used MapBox GL's map and popup events (to improve upon #Jan Dockal solution) which seemed to improve reliability. Also, removed the extra div wrapper.
import { useWorldMap as useMap } from 'hooks/useWorldMap'
import mapboxgl from 'mapbox-gl'
import { FC, useRef, useEffect } from 'react'
export const Popup: FC<{
layerId: string
}> = ({ layerId, children }) => {
const map = useMap() // Uses React Context to get a mapboxgl map (could possibly be null)
const containerRef = useRef<HTMLDivElement>(null)
const popupRef = useRef<mapboxgl.Popup>()
const handleClick = (
e: mapboxgl.MapMouseEvent & {
features?: mapboxgl.MapboxGeoJSONFeature[] | undefined
} & mapboxgl.EventData
) => {
// Bail early if there is no map or container
if (!map || !containerRef.current) {
return
}
// Remove the previous popup if it exists (useful to prevent multiple popups)
if (popupRef.current) {
popupRef.current.remove()
popupRef.current = undefined
}
// Create the popup and add it to the world map
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat) // could also use the coordinates from a feature geometry if the source is in geojson format
.setDOMContent(containerRef.current)
.addTo(map)
// Keep track of the current popup
popupRef.current = popup
// Remove the tracked popup with the popup is closed
popup.on('close', () => {
popupRef.current = undefined
})
}
useEffect(() => {
if (map && layerId) {
// Listen for clicks on the specified layer
map?.on('click', layerId, handleClick)
// Clean up the event listener
return () => {
map?.off('click', layerId, handleClick)
popupRef.current?.remove()
popupRef.current = undefined
}
}
}, [map, layerId])
return <div ref={containerRef}>{children}</div>
}
Try to do with onClick event, instead of creating a button. After that put your react component in onClick events add event listener refrence link
[1]: https://stackoverflow.com/a/64182029/15570982

Categories

Resources