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}
]
});
});
};```
Related
I am fetching data from my device storage and want to display the data on a map. But when I update my state object this.coords.path inside my function showPics() only the testing marker at {lat:1,lng:1} is displayed all the other coordinates are pushed to this.coords.path but not displayed...
I have tried it with the sampleData-array for testing the basic code -> the loop and everything works - just not when updating the state object with new data...
Here is the code:
class Map extends React.Component {
constructor(props) {
super(props);
this.coords = {
path: [
{lat:1, lng:1}
]
}
}
showPics = async() => {
let {value} = await Storage.get({key: 'path' })
let arrayPath = JSON.parse(value)
for( let i=0; i < arrayPath.length; i++) {
let newArray = {lat: arrayPath[i].latitude, lng: arrayPath[i].longitude}
this.coords.path.push(newArray)
}
}
render() {
const sampleData = [{
"Id": 1,
"lat": 54.083336,
"lng: 12.108811
},
{
"Id": 2,
"lat": 54.084336,
"lng": 12.109811
}]
return (
<div className="leaflet-container">
<MapContainer id="map" center={center} zoom={zoom} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{ this.coords.path.map(eachData => (
<Marker
position= {[eachData.lat, eachData.lng]}
/>
))}
<IonFab vertical="bottom" horizontal="end" slot="fixed">
<IonFabButton onClick={() => this.showPics()}>
<IonIcon icon={image}></IonIcon>
</IonFabButton>
</IonFab>
</div>
)
}
}
You are trying to set the data to this.coords. This is not tracked by react and will not be able to update the DOM. Instead you can set your data to the component state as below
state = {
coords: {
path: [
{lat:1, lng:1}
]
}
}
Function showPics can be modified as below to set the data to the state using setState which should be picked by the virtualDOM and update accordingly
showPics = async() => {
const {coords:{path}} = this.state;
let {value} = await Storage.get({key: 'path' })
let arrayPath = JSON.parse(value);
const paths = [...path];
for( let i=0; i < arrayPath.length; i++) {
let newArray = {lat: arrayPath[i].latitude, lng:
arrayPath[i].longitude}
paths.push(newArray)
}
this.setState({
coords: {
path: [...paths]
}
})
}
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
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] };
});
When I navigate my app in startTabBasedApp (My 3 tabs) 1st screen to Render is the EventMap.js this is where my App crash. I just do not know why it crashing I tried to put console.log in all part of my codes but no error appeared.
So the main problem here is in the EventMap.js, because when I tried to remove the EventMap.js in my startTabBasedApp (Main Tabs) then uninstall the app, run react-native run-android, open the App navigate the Tabs (2) it works fine.
What I'm trying to do in my App is when user open the it and navigate it to the EventMap.js I want to get user's location instantly, just like in the Grab App.
How can I achieve this without crashing the App?
Here are my codes for EventMap.js
class EventMap extends Component {
constructor(props) {
super(props);
this.state = {
focusedLocation: {
latitude: 0,
longitude: 0,
latitudeDelta: 0.01,
longitudeDelta: Dimensions.get('window').width / Dimensions.get('window').height * 0.01
},
locationChosen: false,
markerPosition: {
latitude: 0,
longitude: 0
}
}
}
componentDidMount() {
this.didMountGetUserLocation();
};
//This is getting the location and exactly/automatically when they open
didMountGetUserLocation = () => {
navigator.geolocation.getCurrentPosition(pos => {
var lat = parseFloat(pos.coords.latitude)
var long = parseFloat(pos.coords.longitude)
var initialRegion = {
latitude: lat,
longitude: long,
latitudeDelta: 0.01,
longitudeDelta: Dimensions.get('window').width / Dimensions.get('window').height *0.01
};
this.setState({focusedLocation: initialRegion})
this.setState({locationChosen: true})
this.setState({markerPosition: initialRegion})
},
err => {
console.log(err);
});
};
pickLocationHandler = (event) => {
const coords = event.nativeEvent.coordinate;
//For animation of map
this.map.animateToRegion({
...this.state.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
});
this.setState(prevState => {
return {
focusedLocation: {
...prevState.focusedLocation,
latitude: coords.latitude,
longitude: coords.longitude
},
locationChosen: true
};
});
};
getLocationHandler = () => {
navigator.geolocation.getCurrentPosition(pos => {
const coordsEvent = {
nativeEvent: {
coordinate: {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude
}
}
};
this.pickLocationHandler(coordsEvent);
},
err => {
console.log(err);
alert("Fetching failed");
})
};
render() {
let marker = null;
if(this.state.locationChosen) {
marker = <MapView.Marker coordinate={this.state.markerPosition}/>
}
return(
<View style={{zIndex: -1}}>
<TouchableOpacity onPress={this.getLocationHandler} style={styles.iconContainer}>
<Icon name="md-locate" size={30} color="blue"/>
</TouchableOpacity>
<MapView
style={styles.map}
initialRegion={this.state.focusedLocation}
onPress={this.pickLocationHandler}
showsUserLocation={true}
ref={ref => this.map = ref} //For animating map movement
>
{marker}
</MapView>
</View>
);
}
}
I have a screen that loads and places markers on a map based on a search form in the previous screen. I want the map window to also centre itself in the middle of all the markers. So when I use initialRegion, I set the latitude and longitude to state values whose states are set after fetching JSON from a URL. The lat and long are set to values at the centre of the markers. I want the map window to go to these coordinates, but instead, I get an error when the screen loads.
Here is the code:
import React, { Component } from 'react';
import { View, Text, AsyncStorage, Alert, FlatList, StyleSheet } from 'react-native';
import { PrimaryButton } from '../Buttons';
import styles from './styles';
import { ListItem } from '../ListItem';
import MapView, { Marker } from 'react-native-maps';
class RestOptions extends Component {
constructor() {
super();
this.state = {
jsonResults: [],
userPlaces: [],
lat_center: null,
lng_center: null
}
}
renderItem = ({ item }) => {
return (
<View>
<Text>{item.rest_name}</Text>
<Text>{item.counter}</Text>
<Text>Distance: {item.distance} Miles</Text>
<PrimaryButton
label="Set Reservation"
onPress={() => this.setReservation(item.rest_id)}
/>
</View>
)
}
componentDidMount() {
this.getSearchResults();
}
getSearchResults() {
fetch('fetch url here')
.then((response) => response.json())
.then((responseJson) => {
var placesArray = [];
var latArray = [];
var lngArray = [];
for (key = 0; key < responseJson.rest_array.length; key = key + 1) {
var lati_str = responseJson.rest_array[key].lat;
var long_str = responseJson.rest_array[key].lng;
var count_str = responseJson.rest_array[key].counter;
var lati = parseFloat(lati_str);
var long = parseFloat(long_str);
var count = parseFloat(count_str);
latArray.push(lati);
lngArray.push(long);
placesArray.push ({
coordinates: {
latitude: lati,
longitude: long
},
id: count
});
}
var max_lat = Math.max.apply(null, latArray);
var min_lat = Math.min.apply(null, latArray);
var max_lng = Math.max.apply(null, lngArray);
var min_lng = Math.min.apply(null, lngArray);
var latCenter = (max_lat + min_lat) / 2;
var lngCenter = (max_lng + min_lng) / 2;
this.setState({lat_center: latCenter}); //setting latitude state here
this.setState({lng_center: lngCenter}); //setting longitude state here
this.setState({userPlaces: placesArray});
this.setState({jsonResults: responseJson.rest_array});
}).catch((error) => {
console.error(error);
});
}
setReservation(rest_id) {
Alert.alert(rest_id);
//this.props.navigation.navigate('SetReservation');
}
render() {
return (
<View>
<View style={mapStyles.mapContainer}>
<MapView
style={mapStyles.map}
initialRegion={{
latitude: this.state.lat_center, //using latitude state here
longitude: this.state.lng_center, //using longitude state here
latitudeDelta: 0.1022,
longitudeDelta: 0.0821
}}
>
{this.state.userPlaces.map(userPlace => (
<MapView.Marker
coordinate={userPlace.coordinates}
key={userPlace.id}
/>
))}
</MapView>
</View>
<FlatList
data={this.state.jsonResults}
renderItem={this.renderItem}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
};
const mapStyles = StyleSheet.create({
mapContainer: {
width: '100%',
height: 200,
},
map: {
width: '100%',
height: '100%',
},
});
export default RestOptions;
I get this error:
And this warning:
I have already verified that the lat_center and lng_center successfully change state to the appropriate coordinates.
It’s probably because your initial values for the lat_center and lng_center are null in your state object in the constructor.
compondentDidMount gets called after the initial render.
https://reactjs.org/docs/react-component.html#mounting
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
This means that for a moment in time the values of your latitude and longitude will be null leading to these errors. You either need to set an initial value that is not null or not render your map until the coordinates have need set.
Also those 4 setState calls could be reduced to one, in something like this.
this.setState({
lat_center: latCenter,
lng_center: lngCenter,
userPlaces: placesArray,
jsonResults: responseJson.rest_array
});