React with Google Maps Api, how to recenter map - reactjs

I'm using react and using the map as a functional component. (tbh I'm still unsure as to when to use classes v. functions when it comes to classes). however my main issue is that I'm using the google Maps API to present a map and I'm trying to center a map on the users current location. also, I wanted it to update as they walked around so I was just going to use a set interval function to set a timer of when it updates.
I thought that the navigator would be my best bet. Although I can't seem to find a proper function to update the center property after initialization.
I'll mark where I think the function should go.
Here's the documentation I've been looking at:
https://tomchentw.github.io/react-google-maps/#googlemap
function MapContainer() {
const [currentLoc,setCurrentLoc] = useState(
{
lat: 42.331429,
lng: -83.045753
}
)
function setLocation() {
if(navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(
(position) => {
setCurrentLoc(position.coords.latitude,position.coords.longitude)
}
)
}
}
return (
<LoadScript
googleMapsApiKey="Api key"
>
<GoogleMap
//THIS IS WHERE YOU STYLLLLLEEEE
//also where you set what is visible with the controls
options= {{
styles:mapStyles['hide'],
mapTypeControl:false,
disableDefaultUI:true,
draggable:true,
zoomControl:true
}}
id="44b929060bf5f087"
mapContainerStyle=
{{
height: "86.5vh",
width: "100%",
stylers: mapStyles['hide'],
draggable: false
}}
center={{
lat: 44.331429,
lng: -83.045753
}}
zoom={10}
>
{
setInterval((props) => {
var long;
var lati;
if(navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(
(position) => {
lati = position.coords.latitude;
long = position.coords.longitude;
}
)
};
//here is where i'd set the center if i had a function i could do it with
}, 2000)
}
</GoogleMap>
</LoadScript>
)
}
export default MapContainer;

I can't access the documentation link of react-google-maps library. You can use the #react-google-maps/api library since this is a rewrite of the react-google-maps and is more maintained.
For your use case, you can set the value of your center in a state and update it in the setinterval function. This way, each time the state value of the center changes, the center also changes. Please see this sample code and code snippet below:
import React from "react";
import { GoogleMap, LoadScript } from "#react-google-maps/api";
const containerStyle = {
width: "400px",
height: "400px",
};
function MyComponent() {
const [map, setMap] = React.useState(null);
const [currentLoc, setCurrentLoc] = React.useState({
lat: 42.331429,
lng: -83.045753,
});
const onLoad = React.useCallback(function callback(map) {
setMap(map);
setInterval((props) => {
console.log("refreshed");
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
setCurrentLoc({
lat: position.coords.latitude,
lng: position.coords.longitude,
});
});
}
}, 2000);
}, []);
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap
mapContainerStyle={containerStyle}
center={{ lat: currentLoc.lat, lng: currentLoc.lng }}
zoom={10}
onLoad={onLoad}
>
{/* Child components, such as markers, info windows, etc. */}
<></>
</GoogleMap>
</LoadScript>
);
}
export default React.memo(MyComponent);

Related

Geocode in react app not updating the map React

I'm trying to get the map geocode but when the map is loading it never uses the new lat and lng. It's always the original one from the useState.
const CustomMap = () => {
const [customLat, setCustomLat] = useState(0);
const [customLng, setCustomLng] = useState(0);
useEffect(() => {
Geocode.fromAddress("Eiffel Tower").then(
(response) => {
const { lat, lng } = response.results[0].geometry.location;
setCustomLat(lat);
setCustomLng(lng);
},
(error) => {
console.log(error.data);
}
);
}, [])
return (
<GoogleMapReact
bootstrapURLKeys={{ key: 'API_KEY' }}
defaultCenter={{ lat: customLat, lng: customLng }}
defaultZoom={11}>
</GoogleMapReact>
)
}
Even though in the console I can see it's getting the map with url of the Eifell towel.
Fetch finished loading: GET "https://maps.googleapis.com/maps/api/geocode/json?address=Eiffel%20Tower&key=KEY&language=en".
That is the last console log but the center of the map is still the original ones.
You are using the default, this only works on the first render, to update you need use the center, like this
<GoogleMapReact
bootstrapURLKeys={{ key: 'API_KEY' }}
defaultCenter={{ lat: customLat, lng: customLng }}
center = {{lat: customLat, lng: customLng}}
defaultZoom={11}>
</GoogleMapReact>

can't drag to move, zoom in or out google-maps-react

I'm trying to display a couple of markers in a map. On button click, I add in a marker and recenter the map to show all the markers.
function MapContainer(props) {
var bounds = new props.google.maps.LatLngBounds();
const [markers, setMarkers] = useState([
{
lat: 55.378,
lng: 3.436
},
{
lat: 37.0902,
lng: 95.712
}
]);
const adjustMap = (mapProps, map) => {
map.fitBounds(bounds);
}
useEffect(() => {
for (var i = 0; i < markers.length; i++)
bounds.extend(new props.google.maps.LatLng(markers[i].lat, markers[i].lng))
}, [markers])
return (
<>
<div className={props.className}>
<Map
google={props.google}
style={{ borderRadius: '10px'}}
zoom={7}
bounds={bounds}
onBoundsChanged={adjustMap}
>
{
markers.map((item) => {
return (
<Marker position={{lat: item.lat, lng: item.lng}}/>
)
})
}
</Map>
</div>
<button onClick={(e) => { e.preventDefault(); console.log("hehe"); const hoho = [...markers, {lat: 59.913, lng: 10.752}]; setMarkers(hoho)}}>Add marker</button>
</>
);
}
Now the issue is, I can't drag to move the map, nor can I zoom in or out. If I remove the onBoundsChanged property, I can drag and zoom, but if I include it, dragging and zooming doesn't work.
How can I fix this?
You are in an infinite loop since because react is not doing a deep diff on your props and your props are continuously changing because it is a new LatLngBounds object over and over and over. Try passing primitives as props instead of an object.
See https://github.com/googlemaps/js-samples/issues/946 for further discussion around this.

Centralize google map issue

import React, {
useState
} from "react";
import GoogleMapReact from "google-map-react";
function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin[1], pin[0]));
});
return bounds;
};
return ( <
div >
<
div style = {
{
height: "100vh",
width: "100%"
}
} >
<
GoogleMapReact bootstrapURLKeys = {
{
key: AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk
}
}
defaultCenter = {
tallinn.center
}
defaultZoom = {
tallinn.zoom
}
onGoogleApiLoaded = {
({
map,
maps
}) => apiIsLoaded(map, maps, latLgn)
}
yesIWantToUseGoogleMapApiInternals >
{
latLgn.map((item, index) => ( <
div lat = {
item[1]
}
lng = {
item[0]
}
key = {
index
} >
</div>
))
} </GoogleMapReact>
</div>
</div>
);
}
export default Gmap
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have a problem with centralizing the map I am rendering.
although I have a state with the "lat" and "lng" as stated in the docs still, when I run the app with npm start or refreshing the page, it will centre itself somewhere in the ocean.\
PS. I will paste my "map" component only.
import React, { useState } from "react";
import GoogleMapReact from "google-map-react";
export default function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin[1], pin[0]));
});
return bounds;
};
return (
<div>
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{key: AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk }}
defaultCenter={tallinn.center}
defaultZoom={tallinn.zoom}
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps, latLgn)}
yesIWantToUseGoogleMapApiInternals
>
{latLgn.map((item, index) => (
<div lat={item[1]} lng={item[0]} key={index}< </div>
))}
</GoogleMapReact>
</div>
</div>
);
}
ps. let me know if I should provide more information
UPDATE
I have created a sample project here.
I checked your code and I noticed a couple of things.
First, in your getMapBounds function, the bounds returned are null. This is because the values of pin[1] and pin[0] are undefined. This might be the reasoon why the map is centering in the middle of the ocean like this as this is the center of the world and coordinates are 0,0. You must use pin.lat and pin.lng instead so that it will correctly populate the value for bounds.
Second, it seems that you would like to put markers on the coordinates of your latLgn. To achieve this, you can follow the AnyReactComponent function as mentioned in the google-map-react docs instead of putting a div directly inside the latLgn.map.
Lastly, inside your latLgn.map, you must not use item[1] and item[0] as they are also both undefined instead use item.lat and item.lng.
Here's the code snippet for my working code:
import React, { useState } from "react";
import "./style.css"
import GoogleMapReact from "google-map-react";
const AnyReactComponent = ({ text }) => <div>{text}</div>;
export default function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin.lat, pin.lng));
console.log(pin[0])
});
return bounds;
};
return (
<div>
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{key: "YOUR_API_KEY" }}
defaultCenter={tallinn.center}
defaultZoom={tallinn.zoom}
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps, latLgn)}
yesIWantToUseGoogleMapApiInternals
>
{latLgn.map((item, index) => (
<AnyReactComponent lat={item.lat} lng={item.lng} key={index} text={"(" +item.lat + "," + item.lng +")"}> </AnyReactComponent>
))}
</GoogleMapReact>
</div>
</div>
);
}
I also noticed your question in the comment section. You can use online ides like stackblitz to provide an sscce of your code and just remove the API key. You can put a note in your question that you need to put API key to see how the code works. You can refer to my working code above.

Google Maps not re-rendering in 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);

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.

Resources