I have a pretty simple react application using https://github.com/tomchentw/react-google-maps but I'm having difficulty understanding how to get a reference to my current map or how to access the google.maps.Map object in a custom component.
I found this on the repo, but after reading through the posts I'm still a little confused.
I'm starting my application building off of the DirectionsRenderer example.
What I want to do next is add my own custom components for picking the starting point and using the Google Maps autocomplete API.
Yes, I know that the package has a component for that already, but I
need to do a little more than just search for a location on the map.
In order to accomplish my needs I will do something like
const autocomplete = new google.maps.places.Autocomplete(node);
autocomplete.bindTo('bounds', map);
Where node is the element I'm binding the autocomplete functionality and map is an instance of the google.maps.Map object.
My application thus far:
App.jsx
const App = ({ store }) => (
<Provider store={store}>
<div>
<Sidebar>
<StartingPoint defaultText="Choose starting point…" />
</Sidebar>
<GoogleApiWrapper />
</div>
</Provider>
);
GoogleApiWrapper
const GoogleMapHOC = compose(
withProps({
googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=__GAPI_KEY',
loadingElement: <div style={{ height: '100vw' }} />,
containerElement: <div style={{ height: '100vh' }} />,
mapElement: <div style={{ height: '100%' }} />,
}),
withScriptjs,
withGoogleMap,
lifecycle({
componentDidMount() {
const DirectionsService = new google.maps.DirectionsService();
// make google object available to other components
this.props.onLoad(google);
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
ref={props.onMapMounted}
defaultZoom={13}
defaultCenter={new google.maps.LatLng(37.771336, -122.446615)}
>
{props.directions && <DirectionsRenderer directions={props.directions} />}
</GoogleMap>
));
If I'm unable to access the google.maps.Map object outside of the wrapper I would alternatively like to access a reference to the element that contains the map so that I may instantiate a new google.maps.Map(ref_to_elem, options);
Any help would be greatly appreciated!
You can do it by React refs:
<GoogleMap ref={(map) => this._map = map} />
function someFunc () {
//using, for example as:
this._map.getCenter()
this._map.setZoom(your desired zoom);
}
import {GoogleMap, withGoogleMap} from 'react-google-maps';
import {MAP} from 'react-google-maps/lib/constants';
const MapComponent = withGoogleMap(() => (
{/*Here you have access to google.maps.Map object*/}
<GoogleMap ref={(map) => map.context[MAP]}/>
));
const Map = ({locations}) => (
<MapComponentClass
containerElement={MapComponent}
mapElement={MapComponent}
locations={locations}/>
);
export default Map;
Worth pointing out for anyone else googling this that nowdays, using react-google-maps you can simply use the useGoogleMap hook to get access to the Google maps instance
https://react-google-maps-api-docs.netlify.app/#map-instance
import React from 'react'
import { useGoogleMap } from '#react-google-maps/api'
function PanningComponent() {
const map = useGoogleMap()
React.useEffect(() => {
if (map) {
map.panTo(...)
}
}, [map])
return null
}
What I did right now in my react-redux application is I assign global variable map outside of react comnponent GoogleMap:
/*global google*/
// your imports //
var map;
class GoogleMap extends Component {
constructor(props) {
super(props);
this.state = {
// your states
};
}
// your functions
componentWillReceiveProps(nextProps) {
}
componentDidMount() {
// code
// render googlemap
map = new google.maps.Map(this.refs.map, yourMapProps);
// add click event listener to the map
map.addListener('click', function(e) {
//code
});
//viewport listener
map.addListener('idle', function(){
// code
});
}
render() {
return (
<div id="map" ref="map">
{places.map((place) => {
return(<Marker place={place} key={place.key} map={map} />);
})}
</div>
}
}
function mapDispatchToProps(dispatch) {
//code
}
export default connect(mapDispatchToProps)(GoogleMap);
Pass map as props into Child Component:
/*global google*/
import React, { Component } from 'react';
class Marker extends Component {
componentDidMount() {
this.renderMarker();
}
renderMarker() {
var { place, map } = this.props;
place.setMap(map);
}
render() {
return null;
}
}
export default Marker;
I don't know is it good practice. Bu it works. I tried to find the solution how to avoid setting Map Object as global windows.map reading all this stuff about singletons and so on. And then this came to my head. Now if I type window.map in the browser concole I get div id="map"
After thoroughly reading through the react-google-maps documentation, examples, and issues I have come to learn that the package does not support a lot of the things I will need to do for my application.
That being said, I have begun writing my own Google Maps API wrapper based off of the work done by Fullstack React. I've omitted a lot of the utilities used in the below mentioned as they can be found here or here.
That being said my solution is to wrap the google maps container in a higher order component and expose the Map Object via the window object:
App
const App = ({ store }) => (
<Provider store={store}>
<div>
<Sidebar>
<StartingPoint />
{/* TODO */}
</Sidebar>
<GoogleMap />
</div>
</Provider>
);
containers/GoogleMap/wrapper.jsx Google Map Higher Order Component wraps GoogleMap Container
const defaultCreateCache = (options) => {
const opts = options || {};
const apiKey = opts.apiKey;
const libraries = opts.libraries || ['places'];
const version = opts.version || '3.24';
const language = opts.language || 'en';
return ScriptCache({
google: GoogleApi({
apiKey,
language,
libraries,
version,
}),
});
};
const wrapper = options => (WrappedComponent) => {
const createCache = options.createCache || defaultCreateCache;
class Wrapper extends Component {
constructor(props, context) {
super(props, context);
this.scriptCache = createCache(options);
this.scriptCache.google.onLoad(this.onLoad.bind(this));
this.state = {
loaded: false,
google: null,
};
}
onLoad() {
this.GAPI = window.google;
this.setState({ loaded: true, google: this.GAPI });
}
render() {
const props = Object.assign({}, this.props, {
loaded: this.state.loaded,
google: window.google,
});
const mapRef = (el) => { this.map = el; };
return (
<div>
<WrappedComponent {...props} />
<div ref={mapRef} />
</div>
);
}
}
Wrapper.propTypes = {
dispatchGoogleAPI: PropTypes.func,
};
Wrapper.defaultProps = {
dispatchGoogleAPI: null,
};
return Wrapper;
};
export default wrapper;
containers/GoogleMap/index.jsx Google Map Container
class Container extends Component {
constructor(props) {
super(props);
this.loadMap = this.loadMap.bind(this);
this.calcRoute = this.calcRoute.bind(this);
}
componentDidUpdate() {
const { origin, destination, route } = this.props;
this.calcRoute(origin, destination);
}
loadMap(node) {
if (this.props && this.props.google) {
const { google } = this.props;
// instantiate Direction Service
this.directionsService = new google.maps.DirectionsService();
this.directionsDisplay = new google.maps.DirectionsRenderer({
suppressMarkers: true,
});
const zoom = 13;
const mapTypeId = google.maps.MapTypeId.ROADMAP;
const lat = 37.776443;
const lng = -122.451978;
const center = new google.maps.LatLng(lat, lng);
const mapConfig = Object.assign({}, {
center,
zoom,
mapTypeId,
});
this.map = new google.maps.Map(node, mapConfig);
this.directionsDisplay.setMap(this.map);
// make the map instance available to other components
window.map = this.map
}
}
calcRoute(origin, destination) {
const { google, route } = this.props;
if (!origin && !destination && !route) return;
const waypts = [];
waypts.push({
location: new google.maps.LatLng(37.415284, -122.076899),
stopover: true,
});
const start = new google.maps.LatLng(origin.lat, origin.lng);
const end = new google.maps.LatLng(destination.lat, destination.lng);
this.createMarker(end);
const request = {
origin: start,
destination: end,
waypoints: waypts,
optimizeWaypoints: true,
travelMode: google.maps.DirectionsTravelMode.DRIVING,
};
this.directionsService.route(request, (response, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.directionsDisplay.setDirections(response);
const route = response.routes[0];
console.log(route);
}
});
this.props.calculateRoute(false);
}
createMarker(latlng) {
const { google } = this.props;
const marker = new google.maps.Marker({
position: latlng,
map: this.map,
});
}
render() {
return (
<div>
<GoogleMapView loaded={this.props.loaded} loadMap={this.loadMap} />
</div>
);
}
}
const GoogleMapContainer = wrapper({
apiKey: ('YOUR_API_KEY'),
version: '3', // 3.*
libraries: ['places'],
})(Container);
const mapStateToProps = state => ({
origin: state.Trip.origin,
destination: state.Trip.destination,
route: state.Trip.route,
});
const mapDispatchToProps = dispatch => ({
dispatchGoogleMap: (map) => {
dispatch(googleMap(map));
},
calculateRoute: (route) => {
dispatch(tripCalculation(route));
},
});
const GoogleMap = connect(mapStateToProps, mapDispatchToProps)(GoogleMapContainer);
export default GoogleMap;
Related
I used React Google Maps api in one of my Gatsby sites. I created the following component and imported it into one of my pages. Here is the code for the compenent.
import React, { useState } from "react"
import {
GoogleMap,
useLoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api"
import { useStaticQuery, graphql } from "gatsby"
import mapStyles from "./mapStyles"
const Indianapolis = {
lat: 39.768402,
lng: -86.158066,
}
const mapContainerStyle = {
height: "100%",
width: "100%",
}
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
}
const Map = () => {
const data = useStaticQuery(graphql`
{
allKmlPoint {
edges {
node {
properties {
name
Longitude
Latitude
FRP_Project_Numbers
description
styleUrl
styleHash
}
id
}
}
}
}
`)
const [selected, setSelected] = useState(null)
const frpLocation = data.allKmlPoint.edges
//console.log(process.env.GATSBY_GOOGLE_MAPS_API_KEY)
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.GATSBY_GOOGLE_MAPS_API_KEY,
})
const mapRef = React.useRef()
const onMapLoad = React.useCallback(map => {
mapRef.current = map
console.log(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
}, [])
if (loadError) return "Error"
if (!isLoaded) {
return "Loading..."
}
//console.log("comes here")
return (
<div className="map-container">
<span className="top-text">Project</span>
<span className="horizontal-line"></span>
<span className="bottom-text">
Locati<span className="full-color">o</span>ns
</span>
<span className="map-blurb">
FRP has a project portfolio across a wide geographic region. Click the
Map to Zoom and pan to the project locations for various market types.
</span>
<div className="map-wrapper">
<GoogleMap
zoom={8}
center={Indianapolis}
mapContainerStyle={mapContainerStyle}
options={options}
onUnmount={onUnmount}
onLoad={onMapLoad}
>
{frpLocation.map(marker => (
<Marker
key={marker.node.id}
position={{
lat: parseFloat(marker.node.properties.Latitude),
lng: parseFloat(marker.node.properties.Longitude),
}}
icon={{
url: `icon_${marker.node.properties.styleUrl.slice(-6)}.svg`,
origin: new window.google.maps.Point(0, 0),
anchor: new window.google.maps.Point(15, 15),
scaledSize: new window.google.maps.Size(30, 30),
}}
onClick={() => {
setSelected(marker)
}}
/>
))}
{selected ? (
<InfoWindow
position={{
lat: parseFloat(selected.node.properties.Latitude),
lng: parseFloat(selected.node.properties.Longitude),
}}
onCloseClick={() => {
setSelected(null)
}}
>
<div>
<p>{selected.node.properties.name}</p>
</div>
</InfoWindow>
) : null}
</GoogleMap>
</div>
</div>
)
}
export default Map
The page works just fine. However, when I try to move away from the page (which has the google map) to another page (in Gatsby), the page transition is not smooth. Gatsby reloads the new page entirely. The console gives me the following error:
Uncaught TypeError: a is undefined
ZU marker.js:48
<anonymous> marker.js:45
setTimeout handler*_.bn common.js:17
<anonymous> marker.js:45
H js:207
trigger js:204
remove js:207
removeListener js:203
unregisterEvent reactgooglemapsapi.esm.js:142
unregisterEvents reactgooglemapsapi.esm.js:150
componentWillUnmount reactgooglemapsapi.esm.js:2118
wrappedMethod react-hot-loader.development.js:707
React 27
unlisten index.js:103
unlisten index.js:101
promise callback*componentDidMount/refs.unlisten< index.js:99
navigate history.js:100
navigate history.js:99
navigate navigation.js:120
promise callback*navigate navigation.js:84
___navigate navigation.js:162
onClick index.js:256
onClick index.js:477
React 22
marker.js:48:38
There are several instances of this error on the console (I think as many as the number of markers I have on my map).
I am sure it is a simple fix to get rid of this error. Can someone help
UPDATE:Based on what #Ferran said, I used the following code, Still does not work:
I created a useState hook as you said.
const [frpMap, setFrpMap] = useState(null)
mapRef.current = map
...
const onLoad = React.useCallback(function callback(map) {
setFrpMap(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
setFrpMap(null)
mapRef.current = null
//console.log(map)
}, [])
I think I am not sure how to use the map variable set in the setFrpMap hook to render the GoogleMap.
So, when I do setFrpMap(null) on unmount nothing really happens.
You aren't unmounting your map so it breaks when the routing changes. This is not doing anything:
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
}, [])
I would suggest an approach using useState hook, in order to mount and unmount/dispose the map when needed:
import React, { useState } from "react"
import {
GoogleMap,
useLoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api"
import { useStaticQuery, graphql } from "gatsby"
import mapStyles from "./mapStyles"
const Indianapolis = {
lat: 39.768402,
lng: -86.158066,
}
const mapContainerStyle = {
height: "100%",
width: "100%",
}
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
}
const Map = () => {
const [map, setMap] = React.useState(null)
const data = useStaticQuery(graphql`
{
allKmlPoint {
edges {
node {
properties {
name
Longitude
Latitude
FRP_Project_Numbers
description
styleUrl
styleHash
}
id
}
}
}
}
`)
const [selected, setSelected] = useState(null)
const frpLocation = data.allKmlPoint.edges
//console.log(process.env.GATSBY_GOOGLE_MAPS_API_KEY)
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.GATSBY_GOOGLE_MAPS_API_KEY,
})
const mapRef = React.useRef()
const onMapLoad = React.useCallback(map => {
mapRef.current = map
setMap(map)
console.log(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
setMap(null)
}, [])
if (loadError) return "Error"
if (!isLoaded) {
return "Loading..."
}
//console.log("comes here")
return (
<div className="map-container">
<span className="top-text">Project</span>
<span className="horizontal-line"></span>
<span className="bottom-text">
Locati<span className="full-color">o</span>ns
</span>
<span className="map-blurb">
FRP has a project portfolio across a wide geographic region. Click the
Map to Zoom and pan to the project locations for various market types.
</span>
<div className="map-wrapper">
<GoogleMap
zoom={8}
center={Indianapolis}
mapContainerStyle={mapContainerStyle}
options={options}
onUnmount={onUnmount}
onLoad={onMapLoad}
>
{frpLocation.map(marker => (
<Marker
key={marker.node.id}
position={{
lat: parseFloat(marker.node.properties.Latitude),
lng: parseFloat(marker.node.properties.Longitude),
}}
icon={{
url: `icon_${marker.node.properties.styleUrl.slice(-6)}.svg`,
origin: new window.google.maps.Point(0, 0),
anchor: new window.google.maps.Point(15, 15),
scaledSize: new window.google.maps.Size(30, 30),
}}
onClick={() => {
setSelected(marker)
}}
/>
))}
{selected ? (
<InfoWindow
position={{
lat: parseFloat(selected.node.properties.Latitude),
lng: parseFloat(selected.node.properties.Longitude),
}}
onCloseClick={() => {
setSelected(null)
}}
>
<div>
<p>{selected.node.properties.name}</p>
</div>
</InfoWindow>
) : null}
</GoogleMap>
</div>
</div>
)
}
export default Map
The idea is to initially set as null your map and set it in your onLoad function with:
const onMapLoad = React.useCallback(map => {
mapRef.current = map
setMap(map)
console.log(map)
}, [])
Note: adapt the snippet to your needs.
Since the callback is receiving the map as a parameter, you are able to use it along with useState hook.
On the other hand, use the opposite way when unmounting the map (onUnmount):
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
setMap(null) // alternatively use map.data=null
}, [])
The same approach, your callback is receiving the map though you don't need it so, you can set the map object as null.
You can check the docs for further details.
const onUnmount = React.useCallback(function callback(map) {
map.data = null
}, [])
did the trick
I have a code thats will automatically put a marker and route destination if theres a data in the array. But if the array is null its gets an error. How to run a map even if theres no data in my array.
This is my sample code.
googlemap.js
import React, { useState, useEffect } from 'react';
import {
withGoogleMap,
GoogleMap,
withScriptjs,
Marker,
DirectionsRenderer
} from "react-google-maps";
import "./config";
function MapDirectionsRenderer(props) {
const [directions, setDirections] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const { places, travelMode } = props;
const waypoints = places.map(p => ({
location: { lat: p.latitude, lng: p.longitude },
stopover: true
}));
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
console.log(result)
if (status === google.maps.DirectionsStatus.OK) {
setDirections(result);
} else {
setError(result);
}
}
);
});
if (error) {
return <h1>{error}</h1>;
}
return (
directions && (
<DirectionsRenderer directions={directions} />
)
);
}
const Map = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultCenter={props.defaultCenter}
defaultZoom={props.defaultZoom}
>
{props.markers.map((marker, index) => {
const position = { lat: marker.latitude, lng: marker.longitude };
return <Marker key={index} position={position} />;
})}
<MapDirectionsRenderer
places={props.markers}
travelMode={google.maps.TravelMode.DRIVING}
/>
</GoogleMap>
))
);
export default Map;
Map.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import Map from './googlemap';
import "./config";
const googleMapsApiKey = "myapikey";
const places = coordinate;
const App = props => {
const {
loadingElement,
containerElement,
mapElement,
defaultCenter,
defaultZoom
} = props;
return (
<Map
googleMapURL={
'https://maps.googleapis.com/maps/api/js?key=' +
googleMapsApiKey +
'&libraries=geometry,drawing,places'
}
markers={places}
loadingElement={loadingElement || <div style={{height: `100%`}}/>}
containerElement={containerElement || <div style={{height: "80vh"}}/>}
mapElement={mapElement || <div style={{height: `100%`}}/>}
defaultZoom={defaultZoom || 11}
/>
);
};
export default App;
import React, { Component } from 'react';
import { render } from 'react-dom';
import Map from './googlemap';
import "./config";
const googleMapsApiKey = "myapikey";
const places = coordinate;
const App = props => {
const {
loadingElement,
containerElement,
mapElement,
defaultCenter,
defaultZoom
} = props;
return (
<Map
googleMapURL={
'https://maps.googleapis.com/maps/api/js?key=' +
googleMapsApiKey +
'&libraries=geometry,drawing,places'
}
markers={places}
loadingElement={loadingElement || <div style={{height: `100%`}}/>}
containerElement={containerElement || <div style={{height: "80vh"}}/>}
mapElement={mapElement || <div style={{height: `100%`}}/>}
defaultZoom={defaultZoom || 11}
/>
);
};
export default App;
Btw the map will work if there a two routes available if theres 1 or none the map will not show.
I'm traying to draw a route between two points with react-google-maps but is not working for me. Can u help me with this problem? Here's a example of my react component.
import React from 'react';
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
} from 'react-google-maps';
import MapDirectionsRenderer from './app_map_directions_render';
const Map = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultCenter={props.defaultCenter}
defaultZoom={props.defaultZoom}
>
{props.places.map((marker, index) => {
const position = {lat: marker.latitude, lng: marker.longitude};
return <Marker key={index} position={position}/>;
})}
<MapDirectionsRenderer places={props.places} travelMode={window.google.maps.TravelMode.DRIVING} />
</GoogleMap>
))
);
const AppMap = props => {
const {places} = props;
const {
loadingElement,
containerElement,
mapElement,
defaultCenter,
defaultZoom
} = props;
return (
<Map
googleMapURL={
'https://maps.googleapis.com/maps/api/js?key=' +
googleMapsApiKey +
'&v=3.exp&libraries=geometry,drawing,places'
}
places={places}
loadingElement={loadingElement || <div style={{height: `100%`}}/>}
containerElement={containerElement || <div style={{height: "80vh"}}/>}
mapElement={mapElement || <div style={{height: `100%`}}/>}
defaultCenter={defaultCenter || {lat: 25.798939, lng: -80.291409}}
defaultZoom={defaultZoom || 11}
/>
);
};
export default AppMap;
And my MapDirectionsRenderer Component
import React, {Component} from 'react';
import { DirectionsRenderer } from "react-google-maps";
export default class MapDirectionsRenderer extends Component {
state = {
directions: null,
error: null
};
componentDidMount() {
const { places, travelMode } = this.props;
const waypoints = places.map(p =>({
location: {lat: p.latitude, lng: p.longitude},
stopover: true
}))
if(waypoints.length >= 2){
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new window.google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
if (status === window.google.maps.DirectionsStatus.OK) {
this.setState({
directions: result
});
} else {
this.setState({ error: result });
}
}
);
}
}
render() {
if (this.state.error) {
return <h1>{this.state.error}</h1>;
}
return <DirectionsRenderer directions={this.state.directions} />;
}
}
To render a route Google Maps API provides Directions Service, in case of react-google-maps library DirectionsRenderer component is available which is a wrapper around DirectionsRenderer class which in turn:
Renders directions obtained from the DirectionsService.
Assuming the data for route is provided in the following format:
const places = [
{latitude: 25.8103146,longitude: -80.1751609},
{latitude: 27.9947147,longitude: -82.5943645},
{latitude: 28.4813018,longitude: -81.4387899},
//...
]
the following component could be introduced to calculate and render directions via react-google-maps library:
class MapDirectionsRenderer extends React.Component {
state = {
directions: null,
error: null
};
componentDidMount() {
const { places, travelMode } = this.props;
const waypoints = places.map(p =>({
location: {lat: p.latitude, lng:p.longitude},
stopover: true
}))
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: result
});
} else {
this.setState({ error: result });
}
}
);
}
render() {
if (this.state.error) {
return <h1>{this.state.error}</h1>;
}
return <DirectionsRenderer directions={this.state.directions} />;
}
}
Here is a demo
For React 16.8 or above, MapDirectionsRenderer could be implemented (using Hooks) as below:
function MapDirectionsRenderer(props) {
const [directions, setDirections] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const { places, travelMode } = props;
const waypoints = places.map(p => ({
location: { lat: p.latitude, lng: p.longitude },
stopover: true
}));
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
console.log(result)
if (status === google.maps.DirectionsStatus.OK) {
setDirections(result);
} else {
setError(result);
}
}
);
});
if (error) {
return <h1>{error}</h1>;
}
return (
directions && (
<DirectionsRenderer directions={directions} />
)
);
}
I have a React app which uses Google Maps API. I am using Foursquare API also, to fetch data about venues. Currently i am fetching about venues near Nashville, TN, keywords "yoga" and "coffee". I want to use the user's current location, and Nashville as a fallback in case they do not allow.
i've got this from MDN:
var options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
function success(pos) {
var crd = pos.coords;
console.log('Your current position is:');
console.log(`Latitude : ${crd.latitude}`);
console.log(`Longitude: ${crd.longitude}`);
console.log(`More or less ${crd.accuracy} meters.`);
}
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
navigator.geolocation.getCurrentPosition(success, error, options);
and am looking for help implementing this in my code. How do i start with replacing the near: "Nashville, TN", below with the geolocation code? This is my app.js:
import React, { Component } from 'react';
import './App.css';
import SquareAPI from './API/';
import Map from './component/Map';
import SideBar from './component/Sidebar';
class App extends Component {
constructor(){
super();
this.state = {
venues: [],
markers: [],
center: [],
zoom: 14,
updateSuperState: obj => {
this.setState(obj);
}
};
}
closeAllMarkers = () => {
const markers = this.state.markers.map(marker => {
marker.isOpen = false;
return marker;
});
this.setState({ markers: Object.assign(this.state.markers, markers) });
};
handleMarkerClick = marker => {
this.closeAllMarkers();
marker.isOpen = true;
this.setState({ markers: Object.assign(this.state.markers, marker) });
const venue =this.state.venues.find(venue => venue.id === marker.id);
SquareAPI.getVenueDetails(marker.id).then(res => {
const newVenue = Object.assign(venue, res.response.venue);
this.setState({ venues: Object.assign(this.state.venues, newVenue) })
console.log(newVenue);
});
};
handleListItemClick = venue =>{
const marker = this.state.markers.find(marker => marker.id === venue.id)
this.handleMarkerClick(marker)
}
componentDidMount(){
SquareAPI.search({
near:"Nashville, TN",
query: "yoga",
limit: 10
}).then(results => {
const { venues } = results.response;
const { center } = results.response.geocode.feature.geometry;
const markers = venues.map(venue => {
return {
lat: venue.location.lat,
lng: venue.location.lng,
isOpen: false,
isVisible: true,
id: venue.id
};
})
this.setState({ venues, center, markers });
}).catch(error =>{
console.log("Error: " + error)
})
}
render() {
return (
<div className="App">
<SideBar {...this.state} handleListItemClick={this.handleListItemClick}/>
<Map {...this.state}
handleMarkerClick={this.handleMarkerClick}/>
</div>
);
}
}
export default App;
and my Map.js - i may also need to do it at line 10, defaultCenter=...
/* global google */
import React, { Component } from 'react';
import { withScriptjs, withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps';
const MyMapComponent = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultZoom={8}
zoom={props.zoom}
defaultCenter={{ lat: -36.186, lng: -87.066 }}
// defaultCenter={
// }
center={{
lat: parseFloat(props.center.lat),
lng: parseFloat(props.center.lng)
}}
>
{props.markers &&
props.markers.filter(marker => marker.isVisible).map((marker, idx, arr) => {
const venueInfo = props.venues.find(venue => venue.id === marker.id);
return (
<Marker
key={idx}
position={{ lat: marker.lat, lng: marker.lng }}
onClick={() => props.handleMarkerClick(marker)}
animation={arr.length === 1
? google.maps.Animation.BOUNCE
: google.maps.Animation.DROP}
>
{marker.isOpen &&
venueInfo.bestPhoto && (
<InfoWindow>
<React.Fragment>
<img src={`${venueInfo.bestPhoto.prefix}300x300${venueInfo.bestPhoto.suffix}`} alt={venueInfo.name} />
<p>{venueInfo.name}</p>
</React.Fragment>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
))
);
export default class Map extends Component {
render() {
return (
<MyMapComponent
{...this.props}
isMarkerShown
googleMapURL="https://maps.googleapis.com/maps/api/js?key=API_REMOVED"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%`, width: `65%` }} />}
mapElement={<div style={{ height: `100%`}} />}
/>
);
}
}
thanks!
Use the browsers geolocation.
There is an example in the docs.
In terms of React, you would set locations to state, (add a field), pass them to the Map component via prop.
Something like this
class Anything extends Component{
state = {
location : ''
} //no need for constructor no more, these are called class fields.
getPosition= ()=> {
console.log(navigator.gelocation)
//look at example in the docs and then
this.setState(response from navigator)
}
render(){
return (
<Map {...this.state}> // as you are spreading you are good here, access to
// geolocation via this.props.location in map
// component
)
}
}
https://developers.google.com/maps/documentation/javascript/geolocation
I want to make two components: App and Map. However when I try to make a new brand Map component and send the data from App to Map component, I cannot.
My App (default) component holds the data as a state. When I try to send this state to the Map component. It holds the data as a prop.
And of course If I don't separate them and write everything in App.js, everything works as I expected (markers shown on the map). But I want to control all states in the parent component.
Am I violating a fundamental React rule? How can I fix that?
App.js
import React, { Component } from "react";
import "./App.css";
import Map from "./Map";
class App extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
fetch(
"correct_foursquare_api_url"
)
.then(response => response.json())
.then(data =>
data.response.venues.map(place => ({
id: place.id,
name: place.name,
lat: place.location.lat,
lng: place.location.lng
}))
)
.then(locations => {
this.setState({ locations });
});
}
render() {
return (
<div className="App">
<Map locations={this.state.locations} />
</div>
)
}
}
export default App;
Map.js
import React, { Component } from "react";
/* global google */
class Map extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
this.callMap();
}
callMap() {
window.initMap = this.initMap;
loadJS(
"api_url"
);
}
// Map
initMap = () => {
const { locations, markers } = this.state;
let map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 59.4827293, lng: -83.1405355 },
zoom: 13
});
// Markers
for (var i = 0; i < locations.length; i++) {
var title = locations[i].name;
var position = new google.maps.LatLng(locations[i].lat, locations[i].lng);
var id = locations[i].id;
var marker = new google.maps.Marker({
map: map,
position: position,
title: title,
animation: google.maps.Animation.DROP,
id: id
});
markers.push(marker);
}
};
render() {
return <div id="map" />;
}
}
function loadJS(src) {
var ref = window.document.getElementsByTagName("script")[0];
var script = window.document.createElement("script");
script.src = src;
script.async = true;
ref.parentNode.insertBefore(script, ref);
}
export default Map;
You store your locations in the state of the App component, but you also have locations in state of the Map component that you use on mount.
You could instead wait with rendering the Map component until the locations request is finished, and then use the locations props in the Map component passed down from App instead.
Example
class App extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
fetch("correct_foursquare_api_url")
.then(response => response.json())
.then(data => {
const locations = data.response.venues.map(place => ({
id: place.id,
name: place.name,
lat: place.location.lat,
lng: place.location.lng
}));
this.setState({ locations });
});
}
render() {
const { locations, markers } = this.state;
if (locations.length === 0) {
return null;
}
return (
<div className="App">
<Map locations={locations} markers={markers} />
</div>
);
}
}
class Map extends Component {
// ...
initMap = () => {
const { locations, markers } = this.props;
// ...
};
// ...
}