Search Box implementation in React-leaflet v3.1.0 - javascript

I am trying to implement a searchbox feature in my react app. But getting this error "Attempted import error: 'MapControl' is not exported from 'react-leaflet'" in the new version of react-leaflet
import { MapContainer, TileLayer, Polygon, Marker, Popup } from 'react-leaflet';
import "./index.css";
// Cordinates of Marcillac
const center = [45.269169177925754, -0.5231516014256281]
const purpleOptions = { color: 'white' }
class MapWrapper extends React.Component {
render() {
return (
<div id="mapid">
<MapContainer center={center} zoom={13} scrollWheelZoom={true}>
<TileLayer
attribution='© OpenStreetMap © CartoDB'
url='https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png'
/>
</MapContainer>
</div>
)
}
}
export default MapWrapper;
The implementation given here https://stackoverflow.com/questions/48290555/react-leaflet-search-box-implementation doesnt work as MapControl is depricted.
Tried 2nd solution as well.
import { Map, useLeaflet } from 'react-leaflet'
import { OpenStreetMapProvider, GeoSearchControl } from 'leaflet-geosearch'
// make new leaflet element
const Search = (props) => {
const { map } = useLeaflet() // access to leaflet map
const { provider } = props
useEffect(() => {
const searchControl = new GeoSearchControl({
provider,
})
map.addControl(searchControl) // this is how you add a control in vanilla leaflet
return () => map.removeControl(searchControl)
}, [props])
return null // don't want anything to show up from this comp
}
export default function Map() {
return (
<Map {...otherProps}>
{...otherChildren}
<Search provider={new OpenStreetMapProvider()} />
</Map>
)
}
Here I get map.addControl is not defined

Your approach is correct. You have just confused react-leaflet versions.
The way you are doing it would be correct in react-leaflet version 2.x
For react-leaflet v.3.x your custom comp should look like this:
function LeafletgeoSearch() {
const map = useMap(); //here use useMap hook
useEffect(() => {
const provider = new OpenStreetMapProvider();
const searchControl = new GeoSearchControl({
provider,
marker: {
icon
}
});
map.addControl(searchControl);
return () => map.removeControl(searchControl)
}, []);
return null;
}
You can take the map reference from useMap hook instead of useLeaflet.
Demo

Related

Leaflet set default value position

currently appears with the Leaflet map always the error message:
Invalid LatLng object: (undefined, undefined)
This is due to the fact that my variables are not yet available when the map is retrieved.
My code:
import React from "react";
import { TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import StyledMapContainer from "./styled.js";
import { Marker, Popup } from "react-leaflet";
import MarkerIcon from "../mapmarker/index.jsx";
const Map = ({ data }) => {
console.log(data?.latitude);
const position = [data?.latitude, data?.longitude];
return (
<StyledMapContainer
watch
enableHighAccuracy
zoomControl
center={position}
zoom={[13]}
scrollWheelZoom={true}
>
<TileLayer
url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}#2x?access_token="
zoomControl={true}
/>
<Marker position={position} icon={MarkerIcon}>
<Popup>The Ship is here!</Popup>
</Marker>
</StyledMapContainer>
);
};
export default Map;
Now I want to build something so that first a default value is taken and then, once latitude and longitude are available, they are exchanged
I have tried it with:
if (position === "undefined") {
const positon = [37.84151, -6.041185];
}
Unfortunately, this does not work. Do any of you have an idea? Thanks for help!
position may never be equal to "undefined" as this is s a string.
what you want is,
if (position === undefined) {
const positon = [39.86101, -0.069185];
}
But this is still tricky because position can be null.
so i will do this instead.
if (!position) {
const positon = [39.86101, -0.069185];
}
Read more about falsy values of Javascript here.
But in the context of your case, here is how the full code can look like.
import React from "react";
import { TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import StyledMapContainer from "./styled.js";
import { Marker, Popup } from "react-leaflet";
import MarkerIcon from "../mapmarker/index.jsx";
const Map = ({ data }) => {
let position = [39.86101, -0.069185];
if (data?.latitude && data?.longitude) {
const lat = parseFloat(data?.latitude);
const lng = parseFloat(data?.longitude);
if (Number.isFinite(lat) && Number.isFinite(lng)) {
position = [lat, lng];
}
}
return (
<StyledMapContainer
watch
enableHighAccuracy
zoomControl
center={position}
zoom={[13]}
scrollWheelZoom={true}
>
<TileLayer
url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}#2x?access_token="
zoomControl={true}
/>
<Marker position={position} icon={MarkerIcon}>
<Popup>The Ship is here!</Popup>
</Marker>
</StyledMapContainer>
);
};
export default Map;

React Leaflet: Is there a way to make an onClick method that adds a marker and also updates the state with that markers' location? (Beginner React)

I have seen solutions where they use the Map component, but I read that this has been updated to the MapContainer which does not have an onClick Method. Right now, my code (below) allows me to add a marker when I click anywhere on the map. As my title states, how would I store the new marker in some kind of usable variable. My end goal is to store new markers in MongoDB. Thanks in advance.
import React, { Component } from 'react';
import {
MapContainer,
TileLayer,
MapConsumer,
} from "react-leaflet";
import Leaflet from "leaflet";
import { connect } from 'react-redux';
import { Icon } from "../Leaflet/Configurations";
import NavBar from '../components/NavBar';
import Footer from '../components/Footer';
import { registerHouse } from '../actions/houseActions';
import { clearErrors } from "../actions/errorActions";
import "leaflet/dist/leaflet.css";
class MyMap extends Component{
constructor(props){
super(props);
this.state = {
markers: [[40.7, -74]],
data: []
};
this.addMarker = this.addMarker.bind(this);
}
render(){
return(
<div>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossOrigin=""/>
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossOrigin=""></script>
<NavBar/>
{/* <MapContainer className="Map" center={{ lat: 40.7 , lng: -74 }} zoom={15} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<MapConsumer>
{(map) => {
console.log("map center:", map.getCenter());
map.on("click", function (e) {
const { lat, lng } = e.latlng;
Leaflet.marker([lat, lng], { Icon }).addTo(map);
let tmp = this.state.data;
tmp.append([lat,lng]);
this.setState({data:tmp});
});
return null;
}}
</MapConsumer>
</MapContainer> */}
<Footer/>
</div>
)
}
}
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated,
error: state.error
});
export default connect(mapStateToProps, { registerHouse, clearErrors })(MyMap);
I am not sure this part of your code works:
let tmp = this.state.data;
tmp.append([lat,lng]);
this.setState({data:tmp});
why? Because this.state is undefined inside map.on('click) block scope. It loses its reference there.
So what you could do is create a custom child component of MapContainer and take advantage of a concept in react called raising an event from the child to the parent component. The parent will send an event to the child and the latter calls that function when something happens.
function MyComponent({ saveMarkers }) {
const map = useMapEvents({
click: (e) => {
const { lat, lng } = e.latlng;
L.marker([lat, lng], { icon }).addTo(map);
saveMarkers([lat, lng]);
}
});
return null;
}
and in your MyMap comp define the event
saveMarkers = (newMarkerCoords) => {
const data = [...this.state.data, newMarkerCoords];
this.setState((prevState) => ({ ...prevState, data }));
};
which will be passed as a prop to the custom comp:
<MapContainer
...
<MyComponent saveMarkers={this.saveMarkers} />
</MapContainer>
Demo

How to use Leaflet.Polyline.SnakeAnim plugin in React without react-leaflet?

I am using Leaflet in a React Project, and i want to import a plugin which extends Leaflet Polyline which is Leaflet.Polyline.SnakeAnim
I am not using react-leaflet but directly the Javascript library, here is my code :
import React from 'react';
import L from 'leaflet';
import 'leaflet.polyline.snakeanim';
class LeafletMap extends React.Component {
componentDidMount() {
this.map = L.map('leafletMap', {
center:[0,0],
zoom: 2
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
let polylinePoints = [
[57.74, 11.94],
[0,0],
[22,22]
];
L.polyline(polylinePoints, {
color: 'blue',
weight: 8
}).addTo(this.map).snakeIn();
}
}
In my IDE, I got a warning on snakeIn() saying the function is unresolved. And the plugin don't work at all.
How can I properly import a Leaflet Plugin into React ?
If you want to trigger the animation when the map loads then place the snake logic inside componentDidMount otherwise save the mapInstance when the map loads and then , using a button, trigger the snake animation inside componentDidUpdate.
class LeafletMap extends React.Component {
state = {
mapInstance: null,
startAnimation: false
};
startSnake = () => this.setState({ startAnimation: true });
componentDidMount() {
const map = L.map("map").setView([51.505, -0.09], 13);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'© OpenStreetMap contributors'
}).addTo(map);
this.setState({ mapInstance: map });
}
componentDidUpdate(prevProps, prevState) {
if (prevState.startAnimation !== this.state.startAnimation) {
const trd = [63.5, 11];
const mad = [40.5, -3.5];
const lnd = [51.5, -0.5];
const ams = [52.3, 4.75];
const vlc = [39.5, -0.5];
const route = L.featureGroup([
L.marker(trd, { icon }),
L.polyline([trd, ams]),
L.marker(ams, { icon }),
L.polyline([ams, lnd]),
L.marker(lnd, { icon }),
L.polyline([lnd, mad]),
L.marker(mad, { icon }),
L.polyline([mad, vlc]),
L.marker(vlc, { icon })
]);
this.state.mapInstance.fitBounds(route.getBounds());
this.state.mapInstance.addLayer(route);
route.snakeIn();
route.on("snakestart snake snakeend", ev => {
console.log(ev.type);
});
}
}
render() {
return (
<>
<div id="map" style={{ height: "90vh" }} />
<button onClick={this.startSnake}>Snake it!</button>
</>
);
}
}
Demo

Why isn't Google Maps reloading with new coordinates, with the rest of list detail?

I have a list of events, and each event has a click function that displays the details of that event. Each detail holds a google map which takes the latlng coordinates found inside the clicked event object.
My problem is this: when I click on an event for the first time, the detail comes out correctly, with the correct title, location name and map. But then, the second click renders the new detail of the new event with the correct title, location name BUT the map doesn't change.
Why isn't the map being reloaded with the new coordinates?
here is the code for the 'event list' container:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { selectEvent } from '../actions/index.js';
class EventsList extends React.Component {
render() {
var createEventsList = this.props.events.map((event, i) => {
return <li key={i} onClick={() => this.props.selectEvent(event)}>{event.title}</li>
});
return(
<ul>{createEventsList}</ul>
);
}
}
function mapStateToProps(state) {
return {
events: state.events
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ selectEvent: selectEvent }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(EventsList);
the code for the 'event detail' container:
import React from 'react';
import { connect } from 'react-redux';
import GoogleMap from '../components/google_map.js';
class EventDetail extends React.Component{
render() {
if(!this.props.event) {
return <div>Pick an Event</div>
}
const latcoord = parseFloat(this.props.event.locationLat);
const longcoord = parseFloat(this.props.event.locationLong);
return(
<div>
<div>details for:</div>
<div>{ this.props.event.title }</div>
<div>{ this.props.event.location }</div>
<GoogleMap long={longcoord} lat={latcoord}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
event: state.eventDetail
};
}
export default connect(mapStateToProps)(EventDetail);
and here is the code for the Google Map component:
import React from 'react';
export default class GoogleMap extends React.Component{
componentDidMount() {
new google.maps.Map(this.refs.map, {
zoom: 15,
center: {
lat: this.props.lat,
lng: this.props.long
}
});
}
render() {
return(
<div className="google-map" ref="map"></div>
);
}
}
Since componentDidMount gets called only once when the component is mounted, when you click on an event for the second time, the map is not getting updated. To correct this you will need to implement componentDidUpdate() function. Also you should save the map in a state or in this context. You wouldn't want to create a new map always.
componentDidMount() {
this.map = new google.maps.Map(this.refs.map, {
zoom: 15,
center: {
lat: this.props.lat,
lng: this.props.long
}
});
}
componentDidUpdate() {
this.map.panTo(new google.maps.LatLng(this.props.lat, this.props.lng));
}
Hope it helps. :)

React and Google Maps with google-maps-react, cant get to API's methods

Im using google-maps-react, and it works pretty good, but i just cant understand how can i work with Google Maps API's methods inside my component. Now i need to getBounds of rendered map, but cant find any way to do this. Here is the code, any help would be appreciated.
import React from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
const GOOGLE_MAPS_JS_API_KEY='AIzaSyB6whuBhj_notrealkey';
class GoogleMap extends React.Component {
constructor() {
this.state = {
zoom: 13
}
this.onMapClicked = this.onMapClicked.bind(this);
this.test = this.test.bind(this);
}
onMapClicked (props) {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
})
}
}
test(google) {
// Here i tried a lot of ways to get coords somehow
console.log(google.maps.Map.getBounds())
}
render() {
const {google} = this.props;
if (!this.props.loaded) {
return <div>Loading...</div>
}
return (
<Map className='google-map'
google={google}
onClick={this.onMapClicked}
zoom={this.state.zoom}
onReady={() => this.test(google)}
>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: (GOOGLE_MAPS_JS_API_KEY)
})(GoogleMap);
Google Maps Api v 3.30.4
You could try and adapt your requirements to the following example here.
From what i can see a reference is returned using the onReady prop.
For example :
import React from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
const GOOGLE_MAPS_JS_API_KEY='AIzaSyB6whuBhj_notrealkey';
class GoogleMap extends React.Component {
constructor() {
this.state = {
zoom: 13
}
this.onMapClicked = this.onMapClicked.bind(this);
this.handleMapMount = this.handleMapMount.bind(this);
}
onMapClicked (props) {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
})
}
}
handleMapMount(mapProps, map) {
this.map = map;
//log map bounds
console.log(this.map.getBounds());
}
render() {
const {google} = this.props;
if (!this.props.loaded) {
return <div>Loading...</div>
}
return (
<Map className='google-map'
google={google}
onClick={this.onMapClicked}
zoom={this.state.zoom}
onReady={this.handleMapMount}
>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: (GOOGLE_MAPS_JS_API_KEY)
})(GoogleMap);

Categories