How to setState() from within a nested function in React? - reactjs

I'm trying to adapt this example from https://github.com/mapbox/mapbox-react-examples/tree/master/basic,
import React from 'react'
import ReactDOM from 'react-dom'
import mapboxgl from 'mapbox-gl'
mapboxgl.accessToken = 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4M29iazA2Z2gycXA4N2pmbDZmangifQ.-g_vE53SD2WrJ6tFX7QHmA';
class Application extends React.Component {
constructor(props: Props) {
super(props);
this.state = {
lng: 5,
lat: 34,
zoom: 1.5
};
}
componentDidMount() {
const { lng, lat, zoom } = this.state;
const map = new mapboxgl.Map({
container: this.mapContainer,
style: 'mapbox://styles/mapbox/streets-v9',
center: [lng, lat],
zoom
});
map.on('move', () => {
const { lng, lat } = map.getCenter();
this.setState({
lng: lng.toFixed(4),
lat: lat.toFixed(4),
zoom: map.getZoom().toFixed(2)
});
});
}
render() {
const { lng, lat, zoom } = this.state;
return (
<div>
<div className="inline-block absolute top left mt12 ml12 bg-darken75 color-white z1 py6 px12 round-full txt-s txt-bold">
<div>{`Longitude: ${lng} Latitude: ${lat} Zoom: ${zoom}`}</div>
</div>
<div ref={el => this.mapContainer = el} className="absolute top right left bottom" />
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
to a case in which, rather than displaying the map's center, I would like to display the latitude and longitude of the mouse position.
So far, I've managed to simply log it to the console:
import React from 'react';
import mapboxgl from 'mapbox-gl';
mapboxgl.accessToken = 'pk.eyJ1Ijoia3VydHBlZWsiLCJhIjoiY2p6cnVneWdvMHlzeDNqcWo0dm83ZzZ2eiJ9.yUCSreTRcKs12uT5PTCztg';
export default class Map extends React.Component {
componentDidMount() {
this.map = new mapboxgl.Map({
container: this.mapContainer,
style: 'mapbox://styles/mapbox/outdoors-v11',
center: [-119.5591, 37.715],
zoom: 9
});
this.map.on('load', function(e) {
e.target.on('mousemove', function(e) {
console.log(JSON.stringify(e.point));
console.log(JSON.stringify(e.lngLat.wrap()));
});
});
}
componentWillUnmount() {
this.map.remove();
}
render() {
const style = {
position: 'absolute',
top: 0,
bottom: 0,
width: '100%'
};
return <div style={style} ref={el => this.mapContainer = el} />;
}
}
This writes lines like the following to the console:
{"x":972,"y":272}
{"lng":-118.90266689452113,"lat":37.86205552587528}
However, rather than logging the coordinates to the console, I would like to invoke this.setState() like in the example so that I can render the coordinates in a child component.
The problem is, within the on('mousemove', ...) callback function, this is not the component. I've read about using arrow functions (which are lexically scoped) to work around this, but it seems to me that in this case, I need a 'normal' function(e) in order to capture the event.
How can I setState() with the mouse coordinates in this example?

It's possible to use arrow functions just like any other function
this.map.on('load', e => {
e.target.on('mousemove', e => {
this.setState({}) //correct this
})
})

Related

Add tiles from sentinel-hub to mapbox-gl

I tried to add tiles from sentinel-hub to mapbox-gl.
This is the first time I'm doing this and I didn't succeed.
import mapboxgl from 'mapbox-gl';
import "./Map.css"
mapboxgl.accessToken = 'your token';
export default class MapComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
lng: 7.56198,
lat: 47.47607,
zoom: 9,
}
}
componentDidMount() {
const { lat, lng, zoom } = this.state;
map = new mapboxgl.Map({
container: this.mapDiv,
style: 'mapbox://styles/mapbox/streets-v11',
center: [lng, lat],
zoom: zoom
});
map.on('load', () => {
map.addSource('portland', {
'type': 'raster',
'url': 'https://services.sentinel-hub.com/ogc/wmts/{INSTANCE_ID}?REQUEST=GetTile&bbox=bbox-epsg-3857&RESOLUTION=10&LAYER=TRUE-COLOR-S2L2A&TILEMATRIXSET=PopularWebMercator256&TILEMATRIX=10&TILEROW=307&TILECOL=775'
});
map.addLayer({
'id': 'portland',
'source': 'portland',
'type': 'raster'
});
});
};
render() {
return (
<div>
<div ref={e => this.mapDiv = e} className="map"></div>
<nav id="menu"></nav>
</div>
)
}
}
Maybe I need to do something with 'url' address or 'addSourse/addLayer'. Tell me, what is the error and how to fix it?

Invalid LngLat object: (NaN, NaN) in react js , "Mapbox"

The project concept is to get geolocation and showing Mapbox map using API...
import ReactDOM from "react-dom";
import React, { useRef, useEffect } from "react";
import { geolocated } from "react-geolocated";
import mapboxgl from "mapbox-gl";
import fetchFakeData from "./api/fetchFakeData";
import Popup from "./components/Popup";
import "./App.css";
mapboxgl.accessToken ="pk.eyJ1IjoiamFja3Nvbi1rYXNpIiwiYSI6ImNrbzdsaDJvNTFvc3Eycm9pdTRxYmRxZjUifQ.BzA0w0U7lP0Ka3FcKkI_1Q";
const App = (props) => {
const mapContainerRef = useRef(null);
const popUpRef = useRef(new mapboxgl.Popup({ offset: 15 }));
// initialize map when component mounts
useEffect(() => {
const map = new mapboxgl.Map({
container: mapContainerRef.current,
// See style options here: https://docs.mapbox.com/api/maps/#styles
style: "mapbox://styles/mapbox/streets-v11",
center: [props.lat, props.long],
zoom: 12.5
});
// add navigation control (zoom buttons)
map.addControl(new mapboxgl.NavigationControl(), "bottom-right");
map.on("load", () => {
// add the data source for new a feature collection with no features
map.addSource("random-points-data", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
});
// now add the layer, and reference the data source above by name
map.addLayer({
id: "random-points-layer",
source: "random-points-data",
type: "symbol",
layout: {
// full list of icons here: https://labs.mapbox.com/maki-icons
"icon-image": "bakery-15", // this will put little croissants on our map
"icon-padding": 0,
"icon-allow-overlap": true
}
});
});
map.on("moveend", async () => {
// get new center coordinates
const { lng, lat } = map.getCenter();
// fetch new data
const results = await fetchFakeData({ longitude: lng, latitude: lat });
// update "random-points-data" source with new data
// all layers that consume the "random-points-data" data source will be updated automatically
map.getSource("random-points-data").setData(results);
});
// change cursor to pointer when user hovers over a clickable feature
map.on("mouseenter", "random-points-layer", (e) => {
if (e.features.length) {
map.getCanvas().style.cursor = "pointer";
}
});
// reset cursor to default when user is no longer hovering over a clickable feature
map.on("mouseleave", "random-points-layer", () => {
map.getCanvas().style.cursor = "";
});
// add popup when user clicks a point
map.on("click", "random-points-layer", (e) => {
if (e.features.length) {
const feature = e.features[0];
// create popup node
const popupNode = document.createElement("div");
ReactDOM.render(<Popup feature={feature} />, popupNode);
// set popup on map
popUpRef.current
.setLngLat(feature.geometry.coordinates)
.setDOMContent(popupNode)
.addTo(map);
}
});
// clean up on unmount
return () => map.remove();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return <div className="map-container" ref={mapContainerRef} />
};
export default geolocated({
positionOptions: {
enableHighAccuracy: false
},
userDecisionTimeout: 5000
})(App);
class Gps extends React.Component {
constructor() {
super();
this.state = {
latitude: "",
longitude: ""
};
this.getMyLocation = this.getMyLocation.bind(this);
}
componentDidMount() {
this.getMyLocation();
}
getMyLocation() {
const location = window.navigator && window.navigator.geolocation;
if (location) {
location.getCurrentPosition(
(position) => {
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude
});
},
(error) => {
this.setState({
latitude: "err-latitude",
longitude: "err-longitude"
});
}
);
}}
render() {
const { latitude, longitude } = this.state;
return (
<div>
<App lat={latitude} long={longitude} />
</div>
);
}
}
please see this link "codesandbox" : https://codesandbox.io/s/determined-river-7dt0h?file=/src/App.js
props didn't work.
see this lines:
center: [props.lat, props.long]
<App lat={latitude} long={longitude} / >
screenshot
The Problem
The Lat and Lng were initialized to an empty string, but the center property is expecting an array of numbers.
Solution
In Gps component, initialize the lat and long state values to a number as opposed to an empty string
this.state = {
latitude: 38.8951,
longitude: -77.0364
};
In my case, I was using it for web and the issue was causing because of the height of the div in which I was rendering map component. When I set the minHeight for that div, it worked fine for me.

Google Maps React Polygon Issues

I am creating an app that uses wildfire data from APIs and flips it in order to display polygons on a Google Map using the google-maps-react package. I have figured everything out up until returning and displaying the polygon using a function built into the map component. Does anyone want to chime in on what the issue might be? I'd really appreciate some help. Thanks.
import React, { Component } from 'react';
import { Map, GoogleApiWrapper, Polygon } from 'google-maps-react';
const mapStyles = {
margin: 30,
width: '93.75%',
height: '90%',
border: '1px solid #3E1C18',
display: 'inline-block'
};
class FireMap extends Component {
constructor(props) {
super(props)
this.state = {
fires: [],
polygons: []
}
}
componentDidMount() {
fetch('https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Public_Wildfire_Perimeters_View/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json')
.then(res => res.json())
.then(data => {
this.setState({ fires: data.features })
this.state.fires.map((fire) =>{
if (fire.geometry !== null){
let fireCoords = fire.geometry.rings
let trueCoords = []
fireCoords.map((coords) => {
coords.map((pair) => {
let newPair = {lat: pair[1], lng: pair[0]}
return trueCoords.push(newPair)
})
})
this.state.polygons.push(trueCoords);
console.log(this.state.polygons)
}
})
})
}
showPolygons = () => {
this.state.polygons.map((polygon) => {
let firePoly= <Polygon paths={polygon} options={{
fillColor: "#BF5E4B",
fillOpacity: 0.45,
strokeColor: "#6B352A",
strokeOpacity: 0.9,
strokeWeight: 1
}}/>
return firePoly
})
}
render(){
return(
<div className="mapBox">
<Map
google={this.props.google}
zoom={8}
style={mapStyles}
initialCenter={{ lat: 37.7749, lng: -122.4149 }}
>
{this.showPolygons()}
</Map>
</div>
);
}
}
I haven't caught from your comment if you've tried things I've suggested... But you've edited your post with some random (and incorrect) changes. OK, I'll try to post an answer. It's an answer related to the original code because it looks simpler and contains less errors.
This is how I think it should look like:
const coord_pair_to_latlng = ([lat,lng]) => ({ lat, lng })
const convert_ring_coords = ring => ring.map(coord_pair_to_latlng)
class FireMap extends Component {
constructor(props) {
super(props)
this.state = { fires: [] }
}
componentDidMount() {
fetch('https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Public_Wildfire_Perimeters_View/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json')
.then(res => res.json())
.then(data => this.setState({ fires: data.features }))
}
displayFires = () => this.state.fires
.filter(fire => fire.geometry !== null)
.map(fire => fire.geometry.rings)
.map(rings => <Polygon
paths = { rings.reduce((acc, ring) => acc.concat(convert_ring_coords(ring)), []) }
fillColor = "#BF5E4B"
fillOpacity = {0.45}
strokeColor = "#6B352A"
strokeOpacity = {0.9}
strokeWeight = {1}
/>)
render() {
return (
<div className="mapBox">
<Map
google = {this.props.google}
zoom = {8}
style = {mapStyles}
initialCenter = {{ lat: 37.7749, lng: -122.4149 }}
>
{this.displayFires()}
</Map>
</div>
)
}
}
Except for a more functional code style (which you can ignore), basically all I've changed was:
new Polygon() and <Polygon> is not the same thing. You should return the latter. JSX translates to something like React.createElement(Polygon,...) not to new Polygon(...). OK, you've fixed that.
As per docs and per source code, Polygon should be created as
<Polygon
paths = {coords}
fillColor = "#BF5E4B"
fillOpacity = {0.45}
strokeColor = "#6B352A"
strokeOpacity = {0.9}
strokeWeight = {1}
/>
and not as
<Polygon
paths = {coords}
options = {{...}}
/>
your options are ignored
this.displayFires() in componentDidMount does nothing, so it should be removed.
As a side note: also at the time of this.displayFires() call this.state is not changed yet. But it shouldn't change the outcome because, as I said, this.displayFires() have no effect in componentDidMount... but this.state.polygons.push in your new version of code can have an effect... or I'd better say will introduce bugs. You should never do that.

How to change Google Maps Markers CSS in React?

I'm trying to change the CSS of Google Maps Markers in React when one hovers over the marker, but I am really struggling on how to do it.
I have tried messing with the 'event' object extensively to no avail. I have also tried adding an id to the Marker, but this doesn't actually do anything.
position={{
lat: entry.lat,
lng: entry.lng,
}}
animation={window.google.maps.Animation.DROP}
onClick={(event) => window.open(
'https://blah.com/post/' + entry.id, '_blank'
)
}
onMouseOver={(event) => console.log(event)}
/>
I just want the Marker to change in size when the user hovers over it. No page reloading, none of that.
Please take a look at the code below as an example of a marker's mouse over event in React. It's based on this code from this tutorial I've just come across.
Hopefully it helps point you in the right direction and give you ideas on how this feature can be implemented (just one of many different ways). In this case I've achieved the effect you've described by changing the marker's icon's scaledSize.
I also recommend you go through Google's documentation on markers.
map.js
class Map extends Component {
constructor(props) {
super(props);
this.onScriptLoad = this.onScriptLoad.bind(this)
}
onScriptLoad() {
const map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options);
this.props.onMapLoad(map)
}
componentDidMount() {
if (!window.google) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = `https://maps.google.com/maps/api/js?key=`;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
s.addEventListener('load', e => {
this.onScriptLoad()
})
} else {
this.onScriptLoad()
}
}
render() {
return (
<div style={{ width: 500, height: 500 }} id={this.props.id} />
);
}
}
export default Map
index.js
class App extends Component {
constructor() {
super();
}
render() {
return (
<Map
id="myMap"
options={{
center: { lat: 41.0082, lng: 28.9784 },
zoom: 8
}}
onMapLoad={map => {
const marker = new window.google.maps.Marker({
position: { lat: 41.0082, lng: 28.9784 },
map: map,
icon: {
url: "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png", size: new google.maps.Size(20, 32), scaledSize: new google.maps.Size(20, 32), origin: new google.maps.Point(0, 0)
}
});
marker.addListener('mouseover', e => {
marker.setIcon({ url: "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png", scaledSize: new google.maps.Size(30, 42) })
})
marker.addListener('mouseout', e => {
marker.setIcon({ url: "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png", scaledSize: new google.maps.Size(20, 32) })
})
}}
/>
);
}
}
render(<App />, document.getElementById('root'));
Hope this helps!

How to add coordinates to google-maps-react on a user click event?

I'm trying to add a marker on based on a user clicking on the map for the google-maps-react npm. Currently the code below will generate markers and add them to the this.state = { markers:[ ] } and I would like to map them out on the map component. However, the position:event.latLng, will not register the lat and lng and the marker will only be created and inserted into the state with the key: Date.now() and defaultAnimation: 2. Below the code:
import React, { Component } from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
export class MapContainer2 extends Component {
constructor(props){
super(props);
this.state={
lat:null,
lng:null,
markers:[]
}
}
componentDidMount(){
navigator.geolocation.getCurrentPosition(position=>
this.setState({
lat:position.coords.latitude,
lng:position.coords.longitude,
}));
}
mapClicked = (event) =>{
const { markers } = this.state;
this.setState({
markers:[
{
position:event.latLng,
key: Date.now(),
defaultAnimation: 2,
},
...markers
]
})
}
render() {
if (!this.props.loaded) {
return <div>Loading...</div>
}
const style = {
width: '100%',
height: '100vh'
}
return (
<Map
google={this.props.google}
zoom={11}
style={style}
initialCenter={{
lat: this.state.lat,
lng: this.state.lng
}}
center={{
lat: this.state.lat,
lng: this.state.lng
}}
onClick={this.mapClicked}
>
<Marker
title={'Geolocation'}
position={{
lat:this.state.lat,
lng:this.state.lng,
}}
/>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: ('AIzaSyCZ7rgMN34kWkGvr8Pzkf_8nkT7W6gowBA')
})(MapContainer2)
I resolved it by updating the function mapClicked with the following:
mapClicked = (mapProps, map, event) => {
const { markers } = this.state;
const lat = event.latLng.lat();
const lng = event.latLng.lng();
let url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=myapikey`
axios.get(url).then(response => {
this.setState({
googleReverseGeolocation:response.data.results[0].formatted_address,
markers:[{position:{lat:event.latLng.lat(),lng:event.latLng.lng()}}, ...markers],
latClick:lat,
lngClick:lng
});
this.props.onMapClickChange(lat, lng, response.data.results[0].formatted_address);
});
}

Resources