I'm now trying to feed the position of the user through the variable (coords) but every time I pass any variable into onClickUserLoc() the variable has the error
Cannot read property 'lat' of undefined
and when I console.log it states undefined? The coords variable holds an array of location data such as lng and lat but become undefined in onClickUserLoc().
Code:
export default class App extends React.Component {
constructor() {
super();
this.state = {
ready: false,
where: { lat: '', lng: '' },
error: null,
};
this.onClickUserLoc = this.onClickUserLoc.bind(this)
}
componentDidMount() {
let geoOptions = {
enableHighAccuracy: true,
timeOut: 20000,
maximumAge: 60 * 60 * 24,
};
this.setState({ ready: false, error: null });
navigator.geolocation.getCurrentPosition(
this.geoSuccess,
this.geoFailure,
geoOptions
);
}
mapRef = React.createRef();
geoSuccess = (position) => {
console.log(position.coords.latitude);
console.log(position.coords.longitude);
console.log(this.state.where?.lng);
console.log(this.state.where?.lat);
this.setState({
ready: true,
where: { lat: position.coords.latitude, lng: position.coords.longitude
},
});
console.log(this.state.where?.lng);
console.log(this.state.where?.lat);
};
geoFailure = (err) => {
this.setState({ error: err.message });
console.log(this.state.error);
};
onClickUserLoc({ coords }) {
this.mapRef.current.leafletElement.flyTo(coords, 15);
console.log(coords);
}
render() {
const coords = [this.state.where?.lat, this.state.where?.lng];
return (
<>
<Button onPress={this.onClickUserLoc}>
<Map
center={[...]}
zoom={0}>
style={{ height: "90vh" }}
ref={this.mapRef}
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</map>
</>
)
}
If I understand correctly you want to fly to the position you are right now (geolocation). Variable coords variable is defined inside render method. You either pass the coords variable as an argument to button's onPress :
<Button onPress={() => this.onClickUserLoc(coords)}></Button>
but you don't need to destructure it here
onClickUserLoc(coords) { // here no need to destructure it.
this.mapRef.current.leafletElement.flyTo(coords, 15);
}
or use the state variable where directly inside onClickUserLoc without passing any argument:
onClickUserLoc() {
const {
where: { lat, lng }
} = this.state;
this.mapRef.current.leafletElement.flyTo([lat, lng], 15);
}
Demo
Related
I am having trouble using setStates. I stored an array of markers for my Google Map in my state and I am using a for loop to iterate through each marker in order to change the position state of the marker using Google's Geocode API.
Here is my state:
state = {
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
markers: [
{
name: "Costco Wholesale",
address: "9151 Bridgeport Rd, Richmond, BC V6X 3L9",
position: { lat: 0, lng: 0 },
placeID: 'ChIJWc2NzuF0hlQRDu0NNhdQCjM'
} //just trying to get this one to work first before I add in the others
],
busy: []
};
Here is the function(declared inside the class):
findLatLong(){
for(let i = 0; i < this.state.markers.length; i++){
Geocode.fromAddress(this.state.markers[i].address).then(
response => {
const { lati, lngi } = response.results[0].geometry.location;
this.state.markers[i].position.setState({lat: lati, lng: lngi})
}
);
}
}
As you can see, I am passing the address contained in the same array element into the .fromAddress function and then using setState to set the lat and lng to the returned value.
I later call the function after the map renders but before the markers do:
<Map
google={this.props.google}
zoom={14}
style={mapStyles}
initialCenter={{ lat: 49.166590, lng: -123.133569 }}
>
{this.findLatLong}
{this.state.markers.map((marker, index) => (
<Marker
key={index}
onClick={this.onMarkerClick}
name={marker.name}
position={marker.position}
/>
))}
However marker's position state is not changing and is instead remaining as the filler values I passed during the initial state declaration.
Full code if it helps:
import React, { Component } from 'react';
import { Map, GoogleApiWrapper, InfoWindow, Marker } from 'google-maps-react';
import Geocode from 'react-geocode';
const key = '';
Geocode.setApiKey(key);
const mapStyles = {
width: '100%',
height: '100%'
};
export class MapContainer extends Component {
state = {
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
markers: [
{
name: "Costco Wholesale",
address: "9151 Bridgeport Rd, Richmond, BC V6X 3L9",
position: { lat: 0, lng: 0 },
placeID: 'ChIJWc2NzuF0hlQRDu0NNhdQCjM'
}
],
busy: []
};
findLatLong(){
for(let i = 0; i < this.state.markers.length; i++){
Geocode.fromAddress(this.state.markers[i].address).then(
response => {
const { lati, lngi } = response.results[0].geometry.location;
this.state.markers[i].position.setState({lat: lati, lng: lngi})
}
);
}
}
componentDidMount() {
this.getList();
}
getList = () => {
fetch('/api/getList')
.then(res => res.json())
.then(percent => this.setState({ busy: percent }))
}
onMarkerClick = (props, marker, e) =>
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
});
onClose = props => {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
});
}
};
render() {
return (
<Map
google={this.props.google}
zoom={14}
style={mapStyles}
initialCenter={{ lat: 49.166590, lng: -123.133569 }}
>
{this.findLatLong}
{this.state.markers.map((marker, index) => (
<Marker
key={index}
onClick={this.onMarkerClick}
name={marker.name}
position={marker.position}
/>
))}
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
>
<div>
<h4>{this.state.selectedPlace.name}</h4>
<h4>{this.state.busy}</h4>
</div>
</InfoWindow>
</Map>
);
}
}
Thank you in advance!
Attempt to fix #1
.then(
response => {
const { lati, lngi } = response.results[0].geometry.location;
this.setState(oldState => {
const newMarkers = [oldState.markers];
const modifiedMarker = newMarkers[i];
modifiedMarker.lat = lati;
modifiedMarker.lng = lngi;
return {oldState, markers: [newMarkers]};
//How do i implement the modifiedMarkers?
})
UPDATE
Actually it is better if you mutate the state just once and not inside the loop
findLatLong(){
const newMarkers = [...this.state.markers]
for(let i = 0; i < this.state.markers.length; i++){
Geocode.fromAddress(this.state.markers[i].address).then(
response => {
const { lati, lngi } = response.results[0].geometry.location;
newMarkers[i].position.lat = lati;
newMarkers[i].position.lng = lngi;
}
);
}
this.setState(oldState => {
return { ...oldState, markers: [...newMakers] };
});
}
That's no how you mutate the state, it should be something like this:
this.setState(oldState => {
const newMakers = [...oldState.makers];
const modifiedElement = newMakers[i];
modifiedElement.lat = lati;
modifiedElement.lng = lngi;
return { ...oldState, makers: [...newMakers] };
});
I want to display all the coordinates at the same time received via MQTT, but currently the code only displays the newest latitude and longitude pair. Does anyone have any advice?
constructor(props) {
super(props);
this.state = {
coordinates: [
{latitude: 0, longitude: 0}
]
};
};
componentDidMount() {
client.on('connect', () => {
client.subscribe('topic');
});
client.on('message', (_topic, message) => {
var parsedBody = JSON.parse(message.toString());
var mqttLat = parsedBody["latitude"];
var mqttLong = parsedBody["longitude"];
this.setState({
coordinates: [
{latitude: mqttLat, longitude: mqttLong}
]
});
});
};
<View>
<MapView>
{this.state.coordinates.map((marker, i) => (
<Marker
key = {i}
coordinate = {{
latitude: marker.latitude,
longitude: marker.longitude
}}>
</Marker>
))}
</MapView>
</View>
I guess that the problem is with the way you save your coordinates in the state. If you want to keep more coordinates than just one, push them to the coordinates array instead of overriding previous one.
client.on('connect', () => {
client.subscribe('topic');
});
client.on('message', (_topic, message) => {
var parsedBody = JSON.parse(message.toString());
var mqttLat = parsedBody["latitude"];
var mqttLong = parsedBody["longitude"];
this.setState({
coordinates: [
...this.state.coordinates,
{latitude: mqttLat, longitude: mqttLong}
]
});
});
};```
I'm trying to get the info window of the specific markers to open when the marker is clicked. The map and markers are all appearing correctly and when I hover over a marker it shows the correct title however when I click a marker nothing happens. It's definitely recognizing that the marker was clicked as it logs
the message in the console but it just doesn't show the info window. Any help would be greatly appreciated as I've been trying to solve this problem all day.
Thanks
Map.js
import React, { Component } from "react";
import { Map, GoogleApiWrapper, Marker, InfoWindow } from "google-maps-react";
import axios from "axios";
import shortid from "shortid";
class MyMapComponent extends Component {
constructor(props) {
super(props);
this.state = {
fields: [],
location: {
lat: 51.5074,
lng: 0.1278,
},
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
taxis: [
{
companyName: "",
address1: "",
address2: "",
town: "",
postcode: "",
phoneNumber: "",
email: "",
coords: {
lat: "",
lng: "",
},
distance: "",
},
],
};
}
async componentDidMount() {
const { lat, lng } = await this.getcurrentLocation();
this.setState((prev) => ({
fields: {
...prev.fields,
location: {
lat,
lng,
},
},
currentLocation: {
lat,
lng,
},
}));
}
getcurrentLocation() {
this.getNearbyTaxis();
if (navigator && navigator.geolocation) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition((pos) => {
const coords = pos.coords;
resolve({
lat: 51.5074,
lng: 0.1278,
/*
lat: coords.latitude,
lng: coords.longitude,
*/
});
console.log(coords.latitude, coords.longitude);
this.setState({
location: {
lat: 51.5074,
lng: 0.1278,
},
});
});
});
}
return {
lat: 0,
lng: 0,
};
}
addMarker = (location, map) => {
this.setState((prev) => ({
fields: {
...prev.fields,
location,
},
}));
map.panTo(location);
};
getNearbyTaxis = () => {
axios
.get(
`https://api.tfl.gov.uk/Cabwise/search?lat=${this.state.location.lat}&lon=${this.state.location.lng}`
)
.then((res) => {
res.data.Operators.OperatorList.map((taxi) => {
this.setState({
taxis: this.state.taxis.concat({
companyName: taxi.TradingName,
address1: taxi.AddressLine1,
address2: taxi.AddressLine2,
town: taxi.Town,
postcode: taxi.Postcode,
phoneNumber: taxi.BookingsPhoneNumber,
email: taxi.BookingsEmail,
coords: {
lat: taxi.Latitude,
lng: taxi.Longitude,
},
distance: taxi.Distance,
}),
});
});
})
.then(console.log(this.state.taxis));
};
handleToggle = () => {
console.log("marker clicked");
this.setState({
isOpen: true,
});
console.log(this.state.isOpen);
};
render() {
return (
<>
<button onClick={this.getNearbyTaxis}>Get Taxis</button>
<Map
google={this.props.google}
style={{
width: "100%",
height: "100%",
}}
initialCenter={this.state.fields.location}
center={this.state.fields.location}
zoom={14}
//onClick={this.onMapClicked}
key={shortid.generate()}
>
{this.state.taxis.length &&
this.state.taxis.map((taxi) => {
return (
<Marker
title={taxi.companyName}
name={taxi.companyName}
position={taxi.coords}
onClick={this.handleToggle} // marker ID is the key here.
key={shortid.generate()}
>
<InfoWindow
key={shortid.generate()}
visible={this.state.isOpen}
onCloseClick={this.handleToggle}
>
<div key={shortid.generate()}>
<h1 key={shortid.generate()}> hji </h1>
</div>
</InfoWindow>
</Marker>
);
})}
<Marker position={this.state.fields.location} />
</Map>
</>
);
}
}
export default GoogleApiWrapper({
apiKey: "MY_API_KEY",
})(MyMapComponent);
TaxiList.js
import React, { useState } from "react";
import { Map, GoogleApiWrapper, InfoWindow, Marker } from "google-maps-react";
import MyMapComponent from "./Map";
function TaxiList(props) {
return (
<div>
<MyMapComponent />
</div>
);
}
export default TaxiList;
In the class constructor the initial assignment of this.state = {} is missing property isOpen.
Since you pass that boolean to InfoWindow, is the component's visible prop supposed to do what you think it does? If yes, then the code should kind of work now. If no, you might want to change your code.
Aditionally, every Marker will use the same variable, which means that every related InfoWindow will show up. To solve this you might want to make use of activeMarker you have lying around there in the initial assignment of this.state = {}.
Edit: The InfoWindow won't show because it's nested. Either change Marker component, or move it outside. Documentation here: https://reactjs.org/docs/render-props.html
I have read the other questions related to this and tried implementing what the answers were on there.
They recommended adding /*global google */ - did not work
Next was to add const google = window.google; - did not work
I will post the code below and the error.
The error is happening where new google.maps is being called
Map.js:
/* global google */
import { default as React, Component } from 'react';
import raf from 'raf';
import canUseDOM from 'can-use-dom';
const google = window.google;
import { withGoogleMap, GoogleMap, Circle, InfoWindow, Marker } from 'react-google-maps';
import withScriptjs from 'react-google-maps/lib/async/withScriptjs';
const googleMapURL =
'https://maps.googleapis.com/maps/api/js?v=3.27&libraries=places,geometry&key=AIzaSyA7XEFRxE4Lm28tAh44M_568fCLOP_On3k';
const geolocation =
canUseDOM && navigator.geolocation
? navigator.geolocation
: {
getCurrentPosition(success, failure) {
failure("Your browser doesn't support geolocation.");
},
};
const GeolocationExampleGoogleMap = withScriptjs(
withGoogleMap(props =>
<GoogleMap defaultZoom={8} center={props.center}>
{props.center &&
<InfoWindow position={props.center}>
<div>User's Location</div>
</InfoWindow>}
{props.center &&
<Circle
center={props.center}
radius={props.radius}
options={{
fillColor: 'red',
fillOpacity: 0.2,
strokeColor: 'red',
strokeOpacity: 1,
strokeWeight: 1,
}}
/>}
>
{props.markers.map((marker, index) => {
const onClick = () => props.onMarkerClick(marker);
const onCloseClick = () => props.onCloseClick(marker);
return (
<Marker
key={index}
position={marker.position}
title={(index + 1).toString()}
onClick={onClick}
>
{marker.showInfo &&
<InfoWindow onCloseClick={onCloseClick}>
<div>
<strong>
{marker.content}
</strong>
<br />
<em>The contents of this InfoWindow are actually ReactElements.</em>
</div>
</InfoWindow>}
</Marker>
);
})}
</GoogleMap>,
),
);
function generateInitialMarkers() {
const southWest = new google.maps.LatLng(-31.203405, 125.244141);
const northEast = new google.maps.LatLng(-25.363882, 131.044922);
const lngSpan = northEast.lng() - southWest.lng();
const latSpan = northEast.lat() - southWest.lat();
const markers = [];
for (let i = 0; i < 5; i++) {
const position = new google.maps.LatLng(
southWest.lat() + latSpan * Math.random(),
southWest.lng() + lngSpan * Math.random(),
);
markers.push({
position,
content: 'This is the secret message'.split(' ')[i],
showInfo: false,
});
}
return markers;
}
export default class GeolocationExample extends Component {
constructor(props) {
super(props);
super(props);
this.state = {
center: null,
content: null,
radius: 6000,
markers: generateInitialMarkers(),
};
const isUnmounted = false;
handleMarkerClick = this.handleMarkerClick.bind(this);
handleCloseClick = this.handleCloseClick.bind(this);
}
handleMarkerClick(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (marker === targetMarker) {
return {
...marker,
showInfo: true,
};
}
return marker;
}),
});
}
handleCloseClick(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (marker === targetMarker) {
return {
...marker,
showInfo: false,
};
}
return marker;
}),
});
}
componentDidMount() {
const tick = () => {
if (this.isUnmounted) {
return;
}
this.setState({ radius: Math.max(this.state.radius - 20, 0) });
if (this.state.radius > 200) {
raf(tick);
}
};
geolocation.getCurrentPosition(
(position) => {
if (this.isUnmounted) {
return;
}
this.setState({
center: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
content: 'Location found using HTML5.',
});
raf(tick);
},
(reason) => {
if (this.isUnmounted) {
return;
}
this.setState({
center: {
lat: 60,
lng: 105,
},
content: `Error: The Geolocation service failed (${reason}).`,
});
},
);
}
componentWillUnmount() {
this.isUnmounted = true;
}
render() {
return (
<GeolocationExampleGoogleMap
googleMapURL={googleMapURL}
loadingElement={<div style={{ height: '100%' }}>loading...</div>}
containerElement={<div style={{ height: '100%' }} />}
mapElement={<div style={{ height: '100%' }} />}
center={this.state.center}
content={this.state.content}
radius={this.state.radius}
onMarkerClick={this.handleMarkerClick}
onCloseClick={this.handleCloseClick}
markers={this.state.markers}
/>
);
}
}
You are loading async script. When you initialize google constant there is no google object. You can solve this by using 'ref' for map component. Ref callback fired when google object exists. For example:
<GoogleMap defaultZoom={8} center={props.center} ref={()=>{props.onMapLoad}}>...</GoogleMap>
...
constructor(props) {
...
this.onMapLoad = this.onMapLoad.bind(this)
}
...
onMapLoad() {
/*init markers and all objects with "google" here*/
}
...
<GeolocationExampleGoogleMap
onMapLoad={this.onMapLoad}
...
/>
and, of course, do not init google const, use just google object instead,
I am using leaflet with react-leaflet.
OverpassLayer is working when geolocation is enabled. When Geolocation is blocked because I'm on localhost, the app isn't even entering the OverpassLayer component.
App.js
import OverpassLayer from './OverpassLayer'
class App extends React.Component {
state = {
zoom: 16,
position: {
lat: 51.505,
lng: -0.09,
},
mapKey: Math.random(),
overpassLayerKey: Math.random()
}
componentDidMount () {
//center map on user's current position
this.handleGeolocation()
this.refreshOverpassLayer()
}
handleGeolocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
position: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
mapKey: Math.random()
})
}, (err) => {
console.log("Geolocation did not work: " + err)
}
)
} else {
console.log("Geolocation did not work. Navigator.geolocation falsy")
}
}
refreshOverpassLayer = () => {
this.setState({
overpassLayerKey: Math.random()
})
}
render () {
return (
<Map
style={{height: "100vh", width: "100vw"}}
zoom={this.state.zoom}
center={[this.state.position.lat, this.state.position.lng]}
key={this.state.mapKey}
ref='map'
>
<TileLayer
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
attribution='© OpenStreetMap contributors'
/>
<OverpassLayer
key={this.state.overpassLayerKey}
/>
</Map>
)
}
}
OverpassLayer.js
import {LayerGroup} from 'react-leaflet'
import L from 'leaflet'
import OverPassLayer from 'leaflet-overpass-layer'
export default class OverpassLayer extends LayerGroup {
componentWillReceiveProps(nextProps) {
console.log(nextProps.key)
console.log('OverpassLayer receiving props')
const query = '('
+ 'node["amenity"]({{bbox}});'
+ 'way["amenity"]({{bbox}});'
+ 'relation["amenity"]({{bbox}});'
+ ');'
+ 'out body;'
+ '>;'
+ 'out skel qt;'
const opl = new L.OverPassLayer({
'query': query,
'endPoint': 'https://overpass-api.de/api/',
})
nextProps.map.addLayer(opl)
}
}
The issue is componentWillReceiveProps doesn't fire on the first render. I had to addLayer in the constructor as well.