Reposition the center of the map when the location changes? - reactjs

Hi folks I'm using the react-google-maps library. I'm trying to recenter my map (zoom where the marker is) every time my location changes, but I'm getting a bit lost on how to implement the whole thing. I can see the marker being updated, but the map stays on its defaultCenter position.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
GoogleMap,
Marker,
withScriptjs,
withGoogleMap
} from 'react-google-maps';
import environment from '../../config/environment';
class Map extends Component {
static propTypes = {
defaultZoom: PropTypes.number,
center: PropTypes.shape({
lat: PropTypes.number,
lng: PropTypes.number
}),
location: PropTypes.shape({
lat: PropTypes.number,
lng: PropTypes.number
}),
onPositionChanged: PropTypes.func
};
static defaultProps = {
defaultZoom: 14,
center: {
lat: 60.1699,
lng: 24.9384
},
location: {},
onPositionChanged: () => {}
};
constructor(props) {
super(props);
this.mapRef = React.createRef((ref) => {
this.mapRef = ref;
});
}
componenDidUpdate() {
console.log(`I'm about to update with props: ${JSON.strongify(prevProps, undefined, 2)}`);
}
onPositionChanged = (location) => {
console.log(`This the new location onPositionChange:${JSON.stringify(location, undefined, 2)}`);
const newLocation = new window.google.maps.LatLng(location.lat, location.lng);
// [NOTE]: try using the panTo() from googleMaps to recenter the map ? but don't know how to call it.
return (
<Marker
position={newLocation}
/>
);
}
render() {
const {
center,
defaultZoom,
location,
onPositionChanged
} = this.props;
return (
<GoogleMap
className="google-map"
onClick={onPositionChanged(location)}
defaultZoom={defaultZoom}
defaultCenter={center}
ref={this.mapRef}
>
{/* <Marker
position={location}
/> */}
{ this.onPositionChanged(location) }
</GoogleMap>
);
}
}
const SchedulerGoogleMap = withScriptjs(withGoogleMap(Map));
const SchedulerMap = props => (
<SchedulerGoogleMap
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${
environment.GOOGLE_MAPS_API_KEY
}&v=3`}
loadingElement={<div style={{ height: '20vh' }} />}
containerElement={<div style={{ height: '100%' }} />}
mapElement={<div style={{ height: '20vh', width: '100%' }} />}
{...props}
/>
);
export { Map, SchedulerMap, SchedulerGoogleMap };

Simply pass the center prop to your GoogleMap component instead of the defaultCenter prop. The center prop is mutable whereas defaultZoom is not.

This is what it seemed to work for me, just in case any other person runs into the same problem.
... ommited_code
class Map extends Component {
... ommited_code
componentDidUpdate(prevProps) {
if (prevProps.location !== this.props.location) {
this.mapRef.panTo(
new window.google.maps.LatLng(this.props.location.lat, this.props.location.lng)
);
}
}
render() {
const {
center,
defaultZoom,
location
} = this.props;
return (
<GoogleMap
className="google-map"
defaultZoom={defaultZoom}
defaultCenter={center}
ref={(ref) => {
this.mapRef = ref;
}}
>
<Marker position={new window.google.maps.LatLng(location.lat, location.lng)} />
</GoogleMap>
);
}
}
...ommited_code

panTo() is a method of the google.maps.Map class. (https://developers.google.com/maps/documentation/javascript/reference/map#Map.panTo)
It seems to be the function you are looking for, so you need to call it on your google map by referencing the className you set for your map, then give the panTo method the LatLang object you created:
window.google.maps.Map(document.getElementsByClassName("google-map")).panTo(newLocation);

Related

Pass data from functional component to a class component

I am new to react and I try to pass two location coordinates from a functional component to a class component. This is the way that I try to pass data to the class component.
function ViewPost() {
console.log(posts);
const long = posts?.location?.longitude;
console.log(long);
const lat=posts?.location?.latitude;
console.log(lat);
const location=[lat,long];
return(
<SimpleMap loc={location}/>
);
}
export default ViewPost;
In the class component,
const AnyReactComponent = ({ text }) => <div>{text}</div>;
class SimpleMap extends Component {
static defaultProps = {
center: {
lat: 59.95,
lng: 30.33
},
zoom: 11
};
render() {
const {lat, long}=this.props.loc;
console.log(lat);
console.log(long);
return (
<div className="location-box-b" style={{ height: '100vh', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: "" }}
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
>
<AnyReactComponent
lat={this.props.loc.lat}
lng={this.props.loc.long}
text="Seller's Location"
/>
</GoogleMapReact>
</div>
);
}
}
export default SimpleMap;
This is the code that I try to get data from the functional component. When I try to do like this
const {lat, long}=this.props.loc;
console.log(lat);
console.log(long);
print 'undefined' in the console. How do I solve this error?
Your this.props.loc is an array, this is how you defined it here
const location=[lat,long];
but you are trying to get properties from it like from object
const {lat, long}=this.props.loc;
so you have to change it to be an object:
const location={lat,long};

I'm trying to number my Markers and show InfoWindow with google-maps-react

I can't seem to be able to show the index from my json to google-maps-react, I can see all the markers mapped out, but they show the default marker with no window popped out. Here is the code with the <InfoWindow> placed, react was complaining I have to put a parent div, when I do, I don't see any markers currently.
My car2go json is mapping correctly, just not printing out name={content.name}.
My map.js component:
import React, { Component } from "react";
import Car2go from "../data/car2go/vehicles.json";
import { Map, InfoWindow, Marker, GoogleApiWrapper } from "google-maps-react";
export class MapContainer extends Component {
constructor(props) {
super(props);
this.onMarkerClick = this.onMarkerClick.bind(this);
this.state = {
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
name: null
};
}
onMarkerClick(props, marker, e) {
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
});
}
render() {
const google = window.google;
const style = {
width: "70%",
height: "70%",
margin: "0 auto"
};
//const google = window.google;
return (
<Map
google={this.props.google}
style={style}
initialCenter={{
lat: 53.59301,
lng: 10.07526
}}
zoom={12}
onClick={this.onMapClicked}
>
{Car2go.placemarks.map((content, index) => {
return (
<div>
<Marker
title={index}
name={content.name}
position={{
lat: content.coordinates[1],
lng: content.coordinates[0]
}}
onClick={this.onMarkerClick}
name={content.name}
/>
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
>
<div>
<h1>{this.state.selectedPlace.name}</h1>
</div>
</InfoWindow>
</div>
);
})}
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: "xxxxx"
})(MapContainer);
I guess the React is complaining with error which is similar to this one:
React does not recognize the mapCenter prop on a DOM element
If so, the root cause of this error is related with wrapping Marker component with a div container:
<div>
<Marker
...
/>
</div>
and the way how google-maps-react Map component renders children elements. In that case Map props are transferred to div element instead of Marker component. For a more detail refer Unknown Prop Warning article.
To circumvent this error the following approach could be considered:
replace div container with React.Fragment
explicitly transfer map props to Marker component
Here is an example:
class Map extends Component {
constructor() {
super();
this.state = {
map: null
};
this.handleMapReady = this.handleMapReady.bind(this);
}
handleMapReady(mapProps, map) {
this.setState({ map: map });
}
render() {
return (
<Map
google={this.props.google}
className={"map"}
initialCenter={this.props.center}
zoom={this.props.zoom}
onReady={this.handleMapReady}
>
{this.state.map &&
places.map((place, i) => {
const mapProps = Object.assign({}, this.props);
mapProps.map = this.state.map;
return (
<React.Fragment key={i}>
<Marker
{...mapProps}
onClick={this.handleMarkerClick}
position={place.position}
placeIndex={i}
name={place.name}
/>
</React.Fragment>
);
})}
</Map>
);
}
}
But instead of changes described above I would propose another approach, in particular to create a single instance of InfoWindow component and manage it as demonstrated below:
<Map
google={this.props.google}
className={"map"}
initialCenter={this.props.center}
zoom={this.props.zoom}
>
{places.map((place, i) => {
return (
<Marker
key={i}
onClick={this.handleMarkerClick}
position={place.position}
placeIndex={i}
name={place.name}
/>
);
})}
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.handleClose}
>
<div>{this.state.selectedPlace.name}</div>
</InfoWindow>
</Map>
Here is a demo

Alternative routes in react-google-maps

I am using example in react-google-maps library.
const { compose, withProps, lifecycle } = require("recompose");
const {
withScriptjs,
withGoogleMap,
GoogleMap,
DirectionsRenderer,
} = require("react-google-maps");
const MapWithADirectionsRenderer = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIzaSyC4R6AN7SmujjPUIGKdyao2Kqitzr1kiRg&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap,
lifecycle({
componentDidMount() {
const DirectionsService = new google.maps.DirectionsService();
DirectionsService.route({
origin: new google.maps.LatLng(41.8507300, -87.6512600),
destination: new google.maps.LatLng(41.8525800, -87.6514100),
travelMode: google.maps.TravelMode.DRIVING,
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: result,
});
} else {
console.error(`error fetching directions ${result}`);
}
});
}
})
)(props =>
<GoogleMap
defaultZoom={7}
defaultCenter={new google.maps.LatLng(41.8507300, -87.6512600)}
>
{props.directions && <DirectionsRenderer directions={props.directions} />}
</GoogleMap>
);
<MapWithADirectionsRenderer />
I want to enable alternative routes in my map. So I used
provideRouteAlternatives: true
so inside callback function
(result, status) => { }
the result have a property routes which is an array of alternative routes.
How can I render those routes into map ? .. I also want to click on routes and they will change the color from active to inactive. When user select the route I need to send on the server property called
overview_polyline
which is inside of routes array, where each route inside the array has this property.
Thank you very much.
If you only want to render those routes on the map, you could use DirectionsRenderer from that library.
https://tomchentw.github.io/react-google-maps/#directionsrenderer
However, this DirectionsRenderer component can not be fully customized like defining the colors or onClick functions. What you could do is creating a customized Directions component using Marker and Polygon which also come from this library. Below is how I made it:
import React, { Component } from 'react';
import { Polyline, Marker } from 'react-google-maps';
import { pinkA200, blue500 } from 'material-ui/styles/colors';
import ntol from 'number-to-letter';
import _ from 'lodash';
const DirectionMarker = ({ data, isEnd, i, onClick }) => {
const { start_location, end_location } = data;
if (isEnd) {
return [
<Marker onClick={onClick} position={start_location} label={ntol(i)} key="end0" />,
<Marker onClick={onClick} position={end_location} label={ntol(i + 1)} key="end1" />
];
}
return <Marker onClick={onClick} position={start_location} label={ntol(i)} />;
};
const Direction = ({ direction, isEnd, i, onClick, isSelected }) => {
const data = direction.routes[0].legs[0];
const path = data.steps.reduce((sum, current) => _.concat(sum, current.path), []);
return [
<DirectionMarker data={data} onClick={onClick} isEnd={isEnd} i={i} key="marker" />,
<Polyline
onClick={onClick}
path={path}
options={{
strokeColor: isSelected ? pinkA200 : blue500,
strokeOpacity: 0.6,
strokeWeight: 6
}}
key="line"
/>
];
};
class Directions extends Component {
constructor(props) {
super(props);
this.state = { selectedSegment: 0 };
}
render() {
const { directions } = this.props;
if (_.isEmpty(directions)) {
return false;
}
return directions.map((d, i) => {
const directionProps = {
direction: d,
i,
key: i,
onClick: () => this.setState({ selectedSegment: i }),
isEnd: i === directions.length - 1,
isSelected: i === this.state.selectedSegment
};
return <Direction {...directionProps} />;
});
}
}
export default Directions;

Update Google Map based on Geolocation with React

I'm trying to show Google Map with centering the map based on latitude and longitude which are returned by Geolocation. However, the map shows as the default value and not get rendered by Geolocation values. I set latitude and longitude in component state and trying to re-render the component after the state is updated. But it does not work. Below is my code.
MapView.js
import React, { Component } from 'react'
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps'
import MapComponent from './MapComponent'
class MapView extends Component {
constructor(props){
super(props)
this.state = {
currentLatLng: {
lat: 0,
lng: 0
},
isMarkerShown: false
}
}
componentWillUpdate(){
this.getGeoLocation()
}
componentDidMount() {
this.delayedShowMarker()
}
delayedShowMarker = () => {
setTimeout(() => {
this.getGeoLocation()
this.setState({ isMarkerShown: true })
}, 5000)
}
handleMarkerClick = () => {
this.setState({ isMarkerShown: false })
this.delayedShowMarker()
}
getGeoLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
position => {
this.setState({
lat: position.coords.latitude,
lng: position.coords.longitude
})
}
)
} else {
error => console.log(error)
}
}
render() {
return (
<MapComponent
isMarkerShown={this.state.isMarkerShown}
onMarkerClick={this.handleMarkerClick}
currentLocation={this.state.currentLatLng}
/>
)
}
}
export default MapView;
MapComponent.js
import React, { Component } from 'react'
import { compose, withProps } from 'recompose'
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps'
const MapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap
)((props) =>
<GoogleMap
defaultZoom={8}
defaultCenter={{ lat: props.currentLocation.lat, lng: props.currentLocation.lng }}
>
{props.isMarkerShown && <Marker position={{ lat: props.currentLocation.lat, lng: props.currentLocation.lng }} onClick={props.onMarkerClick} />}
</GoogleMap>
)
export default MapComponent
In fact map is not centered since currentLatLng is not getting updated, you might want something like this:
getGeoLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
position => {
console.log(position.coords);
this.setState(prevState => ({
currentLatLng: {
...prevState.currentLatLng,
lat: position.coords.latitude,
lng: position.coords.longitude
}
}))
}
)
} else {
error => console.log(error)
}
}
instead of original getGeoLocation function
According to w3.org, the current location can be obtained with:
function showMap(position) {
// Show a map centered at (position.coords.latitude, position.coords.longitude).
console.log(position.coords.latitude);
console.log(position.coords.longitude);
}
// One-shot position request.
navigator.geolocation.getCurrentPosition(showMap);
This links Geolocation API:
Geolocation API w3.org
This worked for me, location high accuracy.

React-google-maps infowindow `React.Children.only expected to receive a single React element child.`

I am trying to open a infowindow on a specific marker on click, however when i click one it appears that all of them are opening instead, and showing me this error:
React.Children.only expected to receive a single React element child.
This is what my code looks like right now:
import React, { Component } from 'react';
import { GoogleMap, Marker, withGoogleMap, withScriptjs, InfoWindow } from 'react-google-maps'
class Map extends Component {
state = {
isOpen: false
}
handleMarkerClick = () => {
this.setState({ isOpen: true})
}
render() {
return(
<div>
<GoogleMap
defaultZoom={13}
defaultCenter={{ lat: -22.9034778, lng: -43.1264636 }}
>{this.props.markers.map((marker, index) =>(
<Marker
key={index}
position={marker.location}
onClick={this.handleMarkerClick}
>{this.state.isOpen && <InfoWindow onCloseClick={this.handleMarkerClick}/>}
</Marker>
))}
</GoogleMap>
</div>
)
}
}
export default withScriptjs(withGoogleMap(Map))
Start of Edit
I made some changes to try and address the comment, however it isn't working yet, can you give me some hints on what i'm doing wrong, since i made some changes to the top component i will paste it here too:
import React, { Component } from 'react';
import Map from './Map.js'
import List from './List.js'
import escapeRegExp from 'escape-string-regexp'
import sortBy from 'sort-by'
class App extends Component {
state ={
locations:[
{
name: "Paróquia Nossa Senhora das Dores do Ingá",
location: {
lat: -22.9038875,
lng: -43.1252873
},
isOpen:false,
},
{
name: "Museu de Arte Contemporanea de Niteroi",
location: {
lat: -22.9078182,
lng: -43.1262919
},
isOpen:false,
},
{
name: "UFF - Faculdade de Direito",
location: {
lat: -22.9038469,
lng: -43.126024
},
isOpen:false,
},
{
name: "Ponte Rio-Niterói",
location: {
lat: -22.8701,
lng: -43.167
},
isOpen:false,
},
{
name: "Fundação Oscar Niemeyer",
location: {
lat: -22.888533927137285,
lng: -43.12815992250511
},
isOpen:false,
},
{
name: "Campo de São Bento",
location: {
lat: -22.905279,
lng: -43.107759
},
isOpen:false,
}
],
query:''
}
onToggleOpen = (location) => {
this.setState({ isOpen: !this.isOpen })
}
updateQuery = (query) => {
this.setState({ query: query.trim() })
console.log(query)
}
componentDidMount() {}
render() {
const { query, locations } = this.state
let filteredMarkers
if(query) {
const match = new RegExp(escapeRegExp(query), 'i')
filteredMarkers = locations.filter((location) => match.test(location.name))
}else {
filteredMarkers = locations
}
filteredMarkers.sort(sortBy('name'))
return (
<div className="App">
<div style={{height:`5vh`}}>
<input
type='text'
placeholder='Search locations'
value={query}
onChange={(event) => this.updateQuery(event.target.value)}
/>
</div>
<List
markers={filteredMarkers}
/>
<Map
onToggle={this.onToggleOpen}
googleMapURL="https://maps.googleapis.com/maps/api/js?&key=AIzaSyAiqO5W1p5FAFf8RZD11PGigUXSlmVHguQ&v=3"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `80vh` }} />}
mapElement={<div style={{ height: `100%` }} />}
className="Map"
markers={filteredMarkers}
/>
</div>
);
}
}
export default App;
Map.js
import React, { Component } from 'react';
import { GoogleMap, Marker, withGoogleMap, withScriptjs, InfoWindow } from 'react-google-maps'
class Map extends Component {
render() {
return(
<div>
<GoogleMap
defaultZoom={13}
defaultCenter={{ lat: -22.9034778, lng: -43.1264636 }}
>{this.props.markers.map((marker, index) =>(
<Marker
key={index}
position={marker.location}
onClick={() => this.props.onToggle(marker)}
>{marker.isOpen && <InfoWindow onCloseClick={this.ontoggleOpen}>Hello</InfoWindow>}
</Marker>
))}
</GoogleMap>
</div>
)
}
}
export default withScriptjs(withGoogleMap(Map))
The problem i was having with React.Children.only expected to receive a single React element child. was being caused because i didn't set a div inside the infowindow, so simply by adding it this particular problem was solved.
Here is what it used to look like:
<InfoWindow onCloseClick={this.handleMarkerClick}/>
here is what it should look like:
<InfoWindow onCloseClick={()=>this.props.onToggle(marker)}><div>Hello</div></InfoWindow>
or something along these lines.

Resources