Google Maps not re-rendering in reactjs - reactjs

I am trying to show different locations of stores that exists in a database in the based on user search, but at first i need to center the map using user's current latitude and longitude.
I did set the lat and lng to 0 in the state and updated the state values when the component is mounted.
The problem is, the map component does not re-render when this state value is updated.
import React, { Component } from 'react'
import { Map, GoogleApiWrapper, InfoWindow, Marker } from 'google-maps-react';
import { GeneConsumer } from '../../store';
const mapStyles = {
width: '100% !important',
height: '100% !important'
};
class LocationMap extends Component {
constructor(props) {
super(props);
this.state = {
currentLatLng: {
lat: 0,
lng: 0
},
showingInfoWindow: false, //Hides or the shows the infoWindow
activeMarker: {}, //Shows the active marker upon click
selectedPlace: {} //Shows the infoWindow to the selected place upon a marker
}
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
currentLatLng: {
lat: position.coords.latitude,
lng: position.coords.longitude
}
});
},
(error) => {
this.setState({
currentLatLng: {
lat: 6.4494007,
lng: 3.4498444
}
});
}
)
}
render() {
console.log(this.state.currentLatLng.lat)
console.log(this.state.currentLatLng.lng)
return(
<GeneConsumer>
{value => {
const {locations} = value;
const displayMarkers = () => {
if(!locations){
return null;
}
return locations.map((location, index) => {
return <Marker key={index} id={index} position={{
lat: location.latitude,
lng: location.longitude
}}
onClick={() => onMarkerClick(location)}
name = {location.name}
/>
})
}
const onMarkerClick = (props, marker, e) =>
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
});
const onClose = props => {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
});
}
};
return (
<Map
google={this.props.google}
zoom={10}
style={mapStyles}
initialCenter={{ lat: this.state.currentLatLng.lat, lng: this.state.currentLatLng.lng}}
>
{displayMarkers()}
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={onClose()}
>
<div>
<h4>{this.state.selectedPlace.name}</h4>
</div>
</InfoWindow>
</Map>
);
}}
</GeneConsumer>
);
}
}
export default GoogleApiWrapper({
apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
})(LocationMap);

This is by design, updating initialCenter prop does not cause map to re-render:
initalCenter: Takes an object containing latitude and longitude
coordinates. Sets the maps center upon loading.
To re-center the map use center prop along with initialCenter prop. The following example demonstrates how to re-center the map on map click event:
class MapExample extends Component {
state = {
currentCenter: this.props.center //default center
};
handleMapClick = (ref, map, ev) => {
this.setState({ currentCenter: ev.latLng });
};
render() {
return (
<Map
google={this.props.google}
className={"map"}
initialCenter={this.props.center}
center={this.state.currentCenter}
zoom={this.props.zoom}
onClick={this.handleMapClick}
>
<Marker position={this.state.currentCenter} />
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: "--YOUR-KEY-GOES-HERE--"
})(MapExample);

Related

Change this react class based component to a function one with hooks

I'm trying to add a similar feature (an interactive map with popups) to a react application I'm working on. I'm trying to change this into a function component with hooks but have been having some problems (specifically with handleResults and addMarker). I'm relatively new to coding, sorry if this seems really trivial.
import React, { Component } from 'react';
import ReactMapGl, {Marker, Popup} from 'react-map-gl'
import Geocoder from 'react-map-gl-geocoder'
import 'mapbox-gl/dist/mapbox-gl.css';
import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css'
const mapboxToken = 'Your API Key'
class Map3 extends Component {
constructor() {
super()
this.state = {
viewport: {
width: '100vw',
height: '100vh',
latitude: 49.28,
longitude: -123.12,
zoom: 14
},
currMarker: null,
markers: [],
selectedMarker: null
}
this.handleViewportChange = this.handleViewportChange.bind(this)
this.handleGeocoderViewportChange = this.handleGeocoderViewportChange.bind(this)
this.handleResult = this.handleResult.bind(this)
this.addMarker = this.addMarker.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleMarkerClick = this.handleMarkerClick.bind(this)
}
mapRef = React.createRef()
handleViewportChange(viewport) {
this.setState(prevState => ({
viewport: {...prevState.viewport, ...viewport}
}))
}
handleGeocoderViewportChange(viewport) {
const geocoderDefaultOverrides = {transitionDuration: 1000}
return this.handleViewportChange({
...viewport,
...geocoderDefaultOverrides
})
}
handleResult (result) {
this.setState({
currMarker: {
name: result.result.place_name,
latitude: result.result.center[1],
longitude: result.result.center[0]
}
})
}
addMarker() {
const {currMarker} = this.state
this.setState(prevState => ({
markers: [...prevState.markers, currMarker],
currMarker: null
}))
}
handleMarkerClick(marker) {
this.setState({
selectedMarker: marker
})
}
handleClose = () => {
this.setState({
selectedMarker: null
})
}
render() {
const {viewport, markers, selectedMarker} = this.state
return (
<ReactMapGl
ref={this.mapRef}
{...viewport}
onViewportChange={viewport => this.setState({viewport})}
mapboxApiAccessToken={mapboxToken}
mapStyle="mapbox://styles/mapbox/streets-v10"
>
<button className="add-btn" onClick={this.addMarker}>Add</button>
{markers.map((marker, idx) => {
return (
<Marker
key={idx}
latitude={marker.latitude}
longitude={marker.longitude}
onClick={() => this.handleMarkerClick(marker)}
>
<img src="pin.png" alt="marker"/>
</Marker>
)
})
}
<Geocoder
onSelected={this.handleResult}
mapRef={this.mapRef}
placeholder="Search here!"
onViewportChange={this.handleGeocoderViewportChange}
onResult={this.handleResult}
mapboxApiAccessToken={mapboxToken}
position="top-right"
/>
{this.state.selectedMarker &&
<Popup
mapRef={this.mapRef}
latitude={selectedMarker.latitude}
longitude={selectedMarker.longitude}
closeButton={true}
closeOnClick={false}
onClose={this.handleClose}
>
<h3>{selectedMarker.name}</h3>
</Popup>
}
</ReactMapGl>
)
}
}
export default Map3;

Geocode an address and set it as Initial Center of Google Map

So I'm using google maps API to display a map on my react webpage, I know how to set the initial center point and marker by inputting long and lat, however, now I am trying to geocode a text address using the geocoding API, and push that geocode to the map api to have it load as the initial center point. Any guidance on how to do this is appreciated. Thank you. Below is the code I have for the google maps api so far:
import React, { Component } from "react";
import { Map, GoogleApiWrapper, InfoWindow, Marker } from "google-maps-react";
const mapStyles = {
width: "100%",
height: "30%",
};
export class MapContainer extends Component {
state = {
showingInfoWindow: false, // Hides or shows the InfoWindow
activeMarker: {}, // Shows the active marker upon click
selectedPlace: {}, // Shows the InfoWindow to the selected place upon a marker
};
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: -1.2884,
lng: 36.8233,
}}
>
<Marker
onClick={this.onMarkerClick}
name={"Vendor XYZ's Location"}
/>
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
>
<div>
<h4>{this.state.selectedPlace.name}</h4>
</div>
</InfoWindow>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: "apiKEY GOES HERE",
})(MapContainer);
Since you are using the google-maps-library, you can use the center instead of initialCenter when you intend to change your map's center to the Geocoding result. You can then use state variables to manipulate the changes. As for the Geocoding service, you can call it inside componentDidMount function.
Here's a sample code and code snippet below to achieve this:
/*global google*/
import React, { Component } from 'react';
import { Map, GoogleApiWrapper, InfoWindow, Marker } from 'google-maps-react';
const mapStyles = {
width: '100%',
height: '30%'
};
export class MapContainer extends Component {
state = {
showingInfoWindow: false, // Hides or shows the InfoWindow
activeMarker: {}, // Shows the active marker upon click
selectedPlace: {},
markerName: "Vendor XYZ's Location",
center: {
lat: -1.2884,
lng: 36.8233
}
};
componentDidMount = () => {
//Geocoding services
const geocoder = new google.maps.Geocoder();
const address = 'New York';
geocoder.geocode({ address: address }, (results, status) => {
if (status === 'OK') {
this.setState({
center: results[0].geometry.location,
markerName: results[0].formatted_address
});
} else {
alert('Geocode was not successful for the following reason: ' + status);
}
});
};
onMarkerClick = (props, marker, e) => {
console.log(props);
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={this.state.center}
center={this.state.center}
>
<Marker
position={this.state.center}
onClick={this.onMarkerClick}
name={this.state.markerName}
/>
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
>
<div>
<h4>{this.state.selectedPlace.name}</h4>
</div>
</InfoWindow>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: "apiKEY GOES HERE"
})(MapContainer);

How to call another function from withHandlers?

I am trying to implement google maps in my app using react-google-maps package. In map, I am showing multiple Marker and used MarkerCluster.
Until now I have no issue and easily implemented from the doc. But now I want to show InfoWindow if the marker is clicked.
So, I thought of making a function to get the click event and pass the markerId, so I can call the API and get the relevant data for that marker and then put it in infowindow in a tabular manner.
Now, the problem I am facing is:
1) Calling onToggleOpen from onMarkerClick
2) how to set data in infowindow object in onMarkerClick
All this problem I am facing is because i am using HOC i.e recompose.
I am used to Class implement but tried functional implement trying to make it purely stateless.
Reference link: https://tomchentw.github.io/react-google-maps/#infowindow
Following is my code:
import React, { Component } from "react";
import Header from "./Header.js";
import Sidebar from "./Sidebar.js";
import axios from "axios";
import imgmapcluster from "./pins/iconmapcluster.png";
import user from "./pins/user1copy.png";
import { compose, withProps, withHandlers } from "recompose";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
InfoWindow
} from "react-google-maps";
// const fetch = require("isomorphic-fetch");
const {
MarkerClusterer
} = require("react-google-maps/lib/components/addons/MarkerClusterer");
const MapWithAMarkerClusterer = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key=AIzaSyCHi5ryWgN1FcZI-Hmqw3AdxJQmpopYJGk&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `90vh` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withHandlers(
{
onMarkerClustererClick: () => markerClusterer => {
// console.log("markerCluster", markerClusterer);
const clickedMarkers = markerClusterer.getMarkers();
// console.log(`Current clicked markers length: ${clickedMarkers.length}`);
// console.log(clickedMarkers);
},
onMarkerClick: (props) => markerss => {
//calling api and setting info window object
props.isOpen=!props.isOpen //showing error
},
onToggleOpen: ({ isOpen }) => () => ({
isOpen: !isOpen
})
}
),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap
defaultZoom={5}
defaultCenter={{ lat: 22.845625996700075, lng: 78.9629 }}
>
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
styles={[
{
textColor: 'white',
url: imgmapcluster,
height: 68,
lineHeight: 3,
width: 70
}
]}
enableRetinaIcons
gridSize={50}
>
{props.markers.map((marker, index) => (
<Marker
key={index}
icon={user}
onClick={props.onMarkerClick.bind(props,marker)}
position={{ lat: marker.latitude, lng: marker.longitude }}
/>
))}
{props.isOpen && (
<InfoWindow
// position={{ lat: props.infowindow.lat, lng: props.infowindow.lng }}
onCloseClick={props.onToggleOpen}
>
<h4>hello</h4>
</InfoWindow>
)}
</MarkerClusterer>
</GoogleMap>
));
class DemoApp extends React.PureComponent {
componentWillMount() {
this.setState({ markers: [],isOpen:false,infowindow:{} });
}
componentDidMount() {
axios({
url: "http://staging.clarolabs.in:6067/farmerinfo/farmercoordinates",
method: "POST",
data: {
temp: "temp"
},
headers: {
"Content-Type": "application/json"
}
}).then(res => {
this.setState({ markers: res.data.data.list });
});
}
render() {
return <MapWithAMarkerClusterer markers={this.state.markers} isOpen={this.state.isOpen} InfoWindowobject={this.state.InfoWindowobject}/>;
}
}
In order to call a withHandler from another, you need to separate them within two handlers. Also you can make use of withStateHandler and store the infoWindow state
withStateHandlers(
{ infoWindow: null },
{
setInfoWindow: () => (value) => ({ infoWindow: value)
}
),
withHandlers({
onToggleOpen: ({ isOpen }) => () => ({
isOpen: !isOpen
})
}),
withHandlers(
{
onMarkerClustererClick: () => markerClusterer => {
// console.log("markerCluster", markerClusterer);
const clickedMarkers = markerClusterer.getMarkers();
// console.log(`Current clicked markers length: ${clickedMarkers.length}`);
// console.log(clickedMarkers);
},
onMarkerClick: (props) => markerss => {
const { setInfoWindow, onToggleOpen} = props;
//calling api and setting info window object
setInfoWindow({lat: res.lat, lng: res.lng}) // set infoWindow object here
onToggleOpen() // Toggle open state
}
}
),
You probably should use withStateHandlers to handle state. Also, you can write withHandlers more than once, so handlers inside following withHandlers will have access to other handlers.
compose(
// .....
withStateHandlers(
{ isOpen: false },
{
toggleOpen: (state) => () => ({ isOpen: !state.isOpen }),
// or
setOpen: () => (value) => ({ isOpen: value }),
}
),
withHandlers(
// .....
{
onMarkerClick: (props) => markerss => {
// .....
props.toggleOpen();
// .....
},
},
// .....
),
// .....
)

Center AND zoom by prop in react-google-maps?

I am building a Zillow-like map with real estate property points in page body, and filters in page header.
What I need is to center AND zoom react-google-maps map, when selecting Google place from external Google places filter. Currently I am able to center map on place select, but not zoom in (that is because i need to refetch data on every zoom in/out, so need to control zoom, see code below).
I tried to use fitBounds from here, but ref in my code is always undefined.
I have API that fetches points, based on filters, with params like this:
viewPort: {
northEast: { lat: 57.04834755670983, lng: 24.3255340332031 },
southWest: { lat: 56.83635114109457, lng: 23.93826596679685 }
},
priceMin: 0,
priceMax: 500,
propType: "RENT",
numRooms: 3,
...
In header section there is also address filter input, that has Google places autocomplete. When selected, Google provides info about place:
"geometry" : {
...
"viewport" : {
"northeast" : {
"lat" : 56.95662478029149,
"lng" : 24.2054917802915
},
"southwest" : {
"lat" : 56.9539268197085,
"lng" : 24.2027938197085
}
}
...
Here is my Map component:
import React, { Component } from 'react';
import { compose, withProps, withState, withHandlers } from "recompose";
import {
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
import { MarkerClusterer } from "react-google-maps/lib/components/addons/MarkerClusterer";
const defaultMapOptions = {
fullscreenControl: false,
streetViewControl: false,
};
const PropertyMap = compose(
withProps({
loadingElement: <div>Loading...</div>,
containerElement: <div style={{ height: "500px" }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withState('zoom', 'onZoomChange', 11),
withHandlers(() => {
const refs = {
map: undefined,
}
return {
onMapMounted: () => ref => {
refs.map = ref
},
onZoomChanged: ({ onZoomChange, onMapBoundsChange }) => () => {
onZoomChange(refs.map.getZoom())
onMapBoundsChange(refs.map.getBounds());
},
onDragEnd: ({ onMapBoundsChange }) => () => {
onMapBoundsChange(refs.map.getBounds());
},
onMarkerClustererClick: () => (markerClusterer) => {
const clickedMarkers = markerClusterer.getMarkers()
console.log(`Current clicked markers length: ${clickedMarkers.length}`)
console.log(clickedMarkers)
}
}
}),
withGoogleMap
)(props =>
<GoogleMap
defaultCenter={props.defaultCenter ? props.defaultCenter : { lat: 56.9425, lng: 24.1319 }}
zoom={props.zoom}
ref={props.onMapMounted}
onZoomChanged={props.onZoomChanged}
onDragEnd={props.onDragEnd}
defaultOptions={defaultMapOptions}
>
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
enableRetinaIcons
gridSize={60}
key={props.mapUid}
>
{props.features !== null && props.features.map(feature => {
const coord = { lng: feature.geometry.coordinates[0], lat: feature.geometry.coordinates[1] };
return <Marker key={feature.id} position={coord} />;
})}
</MarkerClusterer>
</GoogleMap>
);
export default PropertyMap;
So I think I would like to make it possible to pass zoom param in props, like this:
<PropertyMap
mapUid={this.state.mapUid}
features={this.state.features}
key={this.state.defaultCenter ? `${this.state.defaultCenter.lat}` : '1'}
defaultCenter={this.state.defaultCenter}
defaultZoom={calculateZoomFromViewPort(this.state.viewPort)}
onMapBoundsChange={this.handleMapBoundsChange}
/>
This way I could calculateZoomFromViewPort(this.state.viewPort), but then I need to somehow set it to withState zoom default value.
So what I need is some how pass default zoom value to withState('zoom', 'onZoomChange', 11), (11 is hardcoded, currently). But I am new to recompose package, but is it heavily used in react-google-maps package examples.
If you have other ideas, how to zoom and center map based on viewport & center params from Google place, please share!
fitbounds() is the recommended way to achieve auto zoom. Remember refs always created after first render of the component. I have made changes in your code by adding fitBounds. There might be few syntax errors as I prefer to use react-google-maps like class based utility wrapper. It's easier to manage with redux and component lifecycles. Below is the latest code:
import React, { Component } from 'react';
import { compose, withProps, withState, withHandlers } from "recompose";
import {
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
import { MarkerClusterer } from "react-google-maps/lib/components/addons/MarkerClusterer";
const defaultMapOptions = {
fullscreenControl: false,
streetViewControl: false,
};
const PropertyMap = compose(
withProps({
loadingElement: <div>Loading...</div>,
containerElement: <div style={{ height: "500px" }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withState('zoom', 'onZoomChange', 11),
withHandlers(() => {
const refs = {
map: undefined,
}
return {
onMarkersUpdate: ()=> () => {
if(refs.map){
const mapBounds = new google.maps.LatLngBounds();
//just add markers position into mapBounds
markers.forEach((marker)=>{
mapBounds.extend(marker.position)
});
this.refs.map.fitBounds(mapBbounds);
}
},
onZoomChanged: ({ onZoomChange, onMapBoundsChange }) => () => {
onZoomChange(refs.map.getZoom())
onMapBoundsChange(refs.map.getBounds());
},
onDragEnd: ({ onMapBoundsChange }) => () => {
onMapBoundsChange(refs.map.getBounds());
},
onMarkerClustererClick: () => (markerClusterer) => {
const clickedMarkers = markerClusterer.getMarkers()
console.log(`Current clicked markers length: ${clickedMarkers.length}`)
console.log(clickedMarkers)
}
}
}),
withGoogleMap
)(props =>
<GoogleMap
defaultCenter={props.defaultCenter ? props.defaultCenter : { lat: 56.9425, lng: 24.1319 }}
zoom={props.zoom}
ref='map'
onZoomChanged={props.onZoomChanged}
onDragEnd={props.onDragEnd}
defaultOptions={defaultMapOptions}
>
{props.onMarkersUpdate()}
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
enableRetinaIcons
gridSize={60}
key={props.mapUid}
>
{props.features !== null && props.features.map(feature => {
const coord = { lng: feature.geometry.coordinates[0], lat: feature.geometry.coordinates[1] };
return <Marker key={feature.id} position={coord} />;
})}
</MarkerClusterer>
</GoogleMap>
);
export default PropertyMap;
Please feel free to react out me. If you make a codesandbox for this. It will be easier to fix syntax errors. And Don't miss out fitbounds(). It makes things easier for us.

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