React.js dynamic map directions renderer - javascript

I've been wondering how to make a dynamic map directions/route. I am using this plugin Directions Renderer but this is a static example. I just want to make a route using my input fields.
Here's my code:
/* global google */
import { default as React, Component,} from "react";
import { withGoogleMap, GoogleMap, DirectionsRenderer,} from "../../../lib";
const DirectionsExampleGoogleMap = withGoogleMap(props => (
<GoogleMap
defaultZoom={7}
defaultCenter={props.center}
>
{props.directions && <DirectionsRenderer directions={props.directions} />}
</GoogleMap>
));
export default class DirectionsExample extends Component {
constructor(props) {
super(props);
this.state = {
origin: '',
destination: '',
}
this.handleOrigin = this.handleOrigin.bind(this);
this.handleDestination = this.handleDestination.bind(this);
}
handleOrigin(event) {
event.preventDefault()
this.setState({origin: event.target.value});
}
handleDestination(event) {
event.preventDefault()
this.setState({destination: event.target.value});
}
componentDidMount() {
const DirectionsService = new google.maps.DirectionsService();
DirectionsService.route({
origin: new google.maps.LatLng(this.state.origin),
destination: new google.maps.LatLng(this.state.destination),
travelMode: google.maps.TravelMode.DRIVING,
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: result,
});
} else {
console.error(`error fetching directions ${result}`);
}
});
}
render() {
return (
<input type='text' value={this.state.origin} onChange=
{this.handleOrigin} />
<input type='text' value={this.state.destination} onChange2=
{this.handleDestination}/>
<DirectionsExampleGoogleMap
containerElement={
<div style={{ height: `100%` }} />
}
mapElement={
<div style={{ height: `100%` }} />
}
center={this.state.origin}
directions={this.state.directions}
/>
);
}
}

to shed some light on your issue, I personally use
import PlacesAutocomplete from 'react-places-autocomplete';
import { geocodeByAddress, geocodeByPlaceId } from 'react-places
autocomplete';
which creates a nice autocomplete field. And I also use these two npm packages below to display my maps.
import Map, {GoogleApiWrapper} from 'google-maps-react'
import Marker from 'google-maps-react'
Hopefully this helps in some way, let me know if you want some help using these packages and I can display some examples, cheers.

Related

Unable to access state from render return call - 'Cannot read property 'instructionList' of null'

I am teaching myself React whilst working on a project which uses the react-google-maps package to produce a map with directions from A to B. The map itself works fine, but I've now tried to print the corresponding route directions in html via the return method but cannot get it these instructions to print out.
From my research via Google and StackOverflow I think my issue may either be:
The scope of the 'this' keyword when trying to access my instructionList in the return method. In which case - what would I need to type to access my instructionList array of <li> items?
I've also tried
<ol>{DirectionsService.route.state.instructionList}</ol> and <ol> and {DirectionsComponent.DirectionsService.route.state.instructionList}</ol> which also didn't work
That when the page is loaded, the api response hasn't necessarily been received and thus my instructionList is null and cannot be rendered. In which case - how should this be handled?
Something else I'm unaware of in my syntax (I'm very much a beginner to react, and the react-google-maps package!)
In my code, I've defined an array called instructionList in the state which contains instructions for getting from A to B
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: { ...result },
markers: true
});
this.setState({
instructions: this.state.directions.routes[0].legs[0].steps
});
this.setState({
instructionList: this.state.instructions.map(instruction => {
return <li>instruction.instructions</li>;
})
});
}
I'm then trying to access this array in the class return method - but the error message is saying that instructionList is not defined.
return (
<div>
<DirectionsComponent/>
<div className="route instructions">
<h1>{title}</h1>
<ol>{this.state.instructionList}</ol>
</div>
<NotificationContainer />
</div>
Below is a fuller piece of code if that makes it easier to identify the issue.
class MyMapComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
startLat,
startLng,
finishLat,
finishLng,
transportMode,
title
} = this.props;
const DirectionsComponent = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key=APIKEYGOESHERE", //removed=&callback=initMap
loadingElement: <div style={{ height: `400px` }} />,
containerElement: <div style={{ width: `100%` }} />,
mapElement: <div style={{ height: `400px`, width: `400px` }} />
}),
withScriptjs,
withGoogleMap,
lifecycle({
componentDidMount() {
const DirectionsService = new google.maps.DirectionsService();
DirectionsService.route(
{
origin: new google.maps.LatLng(startLat, startLng),
destination: new google.maps.LatLng(finishLat, finishLng),
travelMode: google.maps.TravelMode[transportMode],
provideRouteAlternatives: true
},
(result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: { ...result },
markers: true
});
this.setState({
instructions: this.state.directions.routes[0].legs[0].steps
});
this.setState({
instructionList: this.state.instructions.map(instruction => {
return <li>instruction.instructions</li>;
})
});
} else {
console.error(
`There was an error fetching directions for the specified journey ${result}`
);
NotificationManager.error(
"Journey cannot be retrieved, please try again",
"Error",
20000
);
}
}
);
}
})
)(props => (
<GoogleMap defaultZoom={3}>
{props.directions && (
<DirectionsRenderer
directions={props.directions}
suppressMarkers={props.markers}
/>
)}
</GoogleMap>
));
return (
<div>
<DirectionsComponent />
<div className="route instructions">
<h1>{title}</h1>
<ol>{this.state.instructionList}</ol>
</div>
<NotificationContainer />
</div>
);
}
}
export default MyMapComponent;
Error message is currently TypeError: Cannot read property 'instructionList' of null
I have played around with the code and researched quite a bit but I'm going round in circles. I'm sure the solution is a quick one but I'm struggling to find it with my limited knowledge of React/react-google-maps so I'm very appreciative of anyone who is able to help :)
You haven't init your component's state. So you can't access a property of state. You need to init it in constructor.
constructor(props){
super(props);
this.state = { instructionList: [] };
}
Updated
You need to define onChangeInstructionList to change MyMapComponent's instructionList inside DirectionsComponent. You also need to move DirectionsComponent to componentDidMount of MyMapComponent to avoid infinite loop because of state changes.
class MyMapComponent {
constructor(props){
super(props);
this.state = {
instructionList: [],
};
this.onChangeInstructionList = this.onChangeInstructionList.bind(this);
}
componentDidMount() {
const {startLat, startLng, finishLat, finishLng, transportMode} = this.props;
const DirectionsComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=APIKEYGOESHERE",//removed=&callback=initMap
loadingElement: <div style={{ height: `400px` }} />,
containerElement: <div style={{ width: `100%` }} />,
mapElement: <div style={{height: `400px`, width: `400px` }} />,
onChangeInstructionList: this.onChangeInstructionList,
}),
withScriptjs,
withGoogleMap,
lifecycle({
componentDidMount() {
const DirectionsService = new google.maps.DirectionsService();
DirectionsService.route({
origin: new google.maps.LatLng(startLat, startLng),
destination: new google.maps.LatLng(finishLat, finishLng),
travelMode: google.maps.TravelMode[transportMode],
provideRouteAlternatives: true
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: {...result},
markers: true
})
this.setState({instructions: this.state.directions.routes[0].legs[0].steps});
this.props.onChangeInstructionList(this.state.instructions.map(instruction => {
return (<li>instruction.instructions</li>);
}));
} else {
console.error(`There was an error fetching directions for the specified journey ${result}`);
NotificationManager.error("Journey cannot be retrieved, please try again", "Error", 20000);
}
});
}
})
)(props =>
<GoogleMap
defaultZoom={3}
>
{props.directions && <DirectionsRenderer directions={props.directions} suppressMarkers={props.markers}/>}
</GoogleMap>
);
this.setState({
DirectionsComponent,
})
}
onChangeInstructionList(newList) {
this.setState({
instructionList: newList,
});
}
render() {
const {title} = this.props;
const { DirectionsComponent, instructionList } = this.state;
return (
<div>
<DirectionsComponent/>
<div className="route instructions">
<h1>{title}</h1>
<ol>{instructionList}</ol>
</div>
<NotificationContainer />
</div>
)
}
}
export default MyMapComponent

How to implement geolocation in a react app with Google Maps JS API instead of hard-coded location?

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

react-google-maps render markers after user submission

React/Redux newbie here. I have a form input that allows a user to enter a doctor issue. It returns a list of doctors from the server via Redux action, and displays the doctor list and a marker for each doctor's location on the map (react-google-maps).
When I click submit, the list of doctors for the correct issue displays, the map is there, but with no markers. I can get the markers on the map to display ONLY after submitting the form, THEN clicking on a doctor from the list to display their details.
Want: Enter a doctor issue and render both the list of doctors and markers on the map when the user clicks submit. Then, select a doctor to see their details page (that's another question, routing to dynamic a detail page).
I think, I need to use a life-cycle method but not sure how to implement it. Or, is there a better way to handle this with Redux?
Doctor component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DoctorSearchForm from '../../containers/doctors/DoctorSearchForm';
import DoctorList from './DoctorList';
import Map from '../maps/Map';
class Doctors extends Component {
constructor(props) {
super(props);
this.state = {
markers: [],
isMarkerShown: false
}
}
componentDidMount() {
this.getMarkers();
}
getMarkers = () => {
let practices = this.props.doctors.map(function(doctor, index) {
return {
title: doctor.profile.first_name + ' ' + doctor.profile.last_name,
location: {
lat: doctor.practices[0].visit_address.lat,
lng: doctor.practices[0].visit_address.lon
}
}
});
this.setState({ markers: practices, isMarkerShown: true });
}
render() {
const { doctors, match } = this.props;
return (
<div>
<DoctorSearchForm getMarkers={this.getMarkers} />
<div className="row">
<div className="col-md-4">
<DoctorList doctors={doctors} match={match} />
</div>
<div className="col-md-8">
<Map
isMarkerShown={this.state.isMarkerShown}
center={{ lat: 45.6318,lng: -122.6716 }}
zoom={12}
markers={this.state.markers}
/>
</div>
</div>
</div>
);
}
}
Doctors.propTypes = {
doctors: PropTypes.array.isRequired,
match: PropTypes.object.isRequired
}
export default Doctors;
DoctorList component:
import React from "react";
import { Route } from 'react-router-dom';
import DoctorItem from './DoctorItem';
import DoctorView from './DoctorView';
class DoctorList extends React.Component {
render() {
const { doctors, match } = this.props;
const linkList = doctors.map((doctor, index) => {
return (
<DoctorItem doctor={doctor} match={match} key={index} />
);
});
return (
<div>
<h3>DoctorList</h3>
<ul>{linkList}</ul>
<Route path={`${match.url}/:name`}
render={ (props) => <DoctorView data= {this.props.doctors} {...props} />}
/>
</div>
);
}
}
export default DoctorList;
DoctorItem component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, Route } from 'react-router-dom';
import DoctorView from './DoctorView';
const DoctorItem = (props) => {
const { doctor, match } = props;
return (
<li>
<Link to={{ pathname: `${match.url}/${doctor.profile.first_name}-${doctor.profile.last_name}` }}>
{doctor.profile.first_name} {doctor.profile.last_name}
</Link>
</li>
)
}
DoctorItem.propTypes = {
doctor: PropTypes.object.isRequired,
};
export default DoctorItem;
DoctorView component:
import React from 'react';
const DoctorView = ({ data, match }) => {
const doctor = data.find(p => `${p.profile.first_name}-${p.profile.last_name}` === match.params.name);
let doctorData;
if (doctor) {
const mappedSpecialties = Object.entries(doctor.specialties).map(([index, specialty]) => {
return <li key={index} id={index}>{specialty.description}</li>;
});
doctorData =
<div>
<h5><strong>{doctor.profile.first_name} {doctor.profile.last_name}</strong> - {doctor.profile.title}</h5>
<img src={doctor.profile.image_url} alt={"Dr." + doctor.profile.first_name + " " + doctor.profile.last_name} />
<ul>{mappedSpecialties}</ul>
<p>{doctor.profile.bio}</p>
</div>;
}
return (
<div>
{doctorData}
</div>
)
}
export default DoctorView;
Map component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
import { compose, withProps } from "recompose";
export default Map = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key={MY_KEY}&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={9} defaultCenter={{ lat: 45.6318,lng: -122.6716 }}>
{props.markers.map((doctor, index) => {
const marker = {
position: { lat: doctor.location.lat, lng: doctor.location.lng },
title: doctor.title
}
return <Marker key={index} {...marker} />;
})}
</GoogleMap>
));
I've spent several days trying and searching for answers but no luck. Any help would be greatly appreciated!
Just like you calculate the markers when the component mounts, you need to recalculate your markers when you receive new props:
componentWillReceiveProps(nextProps) {
this.getMarkers(nextProps);
}
This will require you to change your getMarkers signature a bit so that it can accept an argument and use that instead of this.props in your map operation:
getMarkers = (props = this.props) => {
let practices = props.doctors.map(function(doctor, index) {
return {
title: doctor.profile.first_name + ' ' + doctor.profile.last_name,
location: {
lat: doctor.practices[0].visit_address.lat,
lng: doctor.practices[0].visit_address.lon
}
}
});
this.setState({ markers: practices, isMarkerShown: true });
}
Assuming you are calling getMarkers() in your DoctorSearchForm component, you can remove that since it will automatically update the markers when receiving new props -- or you could bypass state altogether and just calculate it on the fly in render based on the incoming props.

How to access google.maps.Map object with react-google-maps

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;

React.js - react-google-maps events return

I'm using the react-google-maps package in React but I'm struggling to get the onDrag event from the component. My code is as follows:
import React, {Component} from 'react';
import GoogleAddressAutocomplete from './googleaddressautocomplete.js';
import { withGoogleMap, GoogleMap, Marker } from "react-google-maps";
import axios from 'axios';
import NavWrapper from './navwrapper.js';
class MapPage extends Component {
constructor(props) {
super(props);
this.state = {
markers: {
position: {
lat: 48.723627,
lng: 21.254199900000003,
},
key: Date.now(),
defaultAnimation: 2,
},
mapCenter: { lat: 48.723627, lng: 21.254199900000003 },
access_token: '',
address: ''
}
}
handleMapClick = this.handleMapClick.bind(this);
handleMapDrag = this.handleMapDrag.bind(this);
handleMapLoad = this.handleMapLoad.bind(this);
handleMapClick(event) {
let that = this;
this.setState({
markers: {
position: event.latLng,
defaultAnimation: 2,
key: Date.now()
},
mapCenter: event.latLng
});
}
handleAddressChange(latLngObject, address) {
console.log('addr: => '+address);
}
handleMapDrag(event) {
console.log(event);
}
handleMapLoad(event) {
console.log(event);
}
render() {
const GoogleMapWrapper = withGoogleMap(props => (
<GoogleMap
ref={props.onMapDrag}
defaultZoom={13}
defaultCenter={props.center}
onClick={props.onMapClick}
onDragEnd={props.onMapDrag}
>
<Marker {...props.markers} />
</GoogleMap>
));
return (
<div className="row-100">
<NavWrapper/>
<GoogleAddressAutocomplete addressChange={this.handleAddressChange.bind(this)} address={this.state.address}/>
<br />
<GoogleMapWrapper
containerElement={
<div style={{ height: `50vh` }} />
}
mapElement={
<div style={{ height: `50vh` }} />
}
onMapClick={this.handleMapClick.bind(this)}
onMapDrag={this.handleMapDrag.bind(this)}
onMapLoad={this.handleMapLoad.bind(this)}
markers={this.state.markers}
center={this.state.mapCenter}
/>
</div>
)
}
}
export default MapPage;
The handleMapDrag(event) function still returns 'undefined'. Could you please help? I need to get the map's center in LatLng format after the map is dragged, either on the onDragEnd event or the onDrag event itself.
Thanks
So the solution was to call the lat&lng functions on the actual instance, note the 'ref' property on the GoogleMaps object, it's important for proper function.
import React, {Component} from 'react';
import GoogleAddressAutocomplete from './googleaddressautocomplete.js';
import { withGoogleMap, GoogleMap, Marker } from "react-google-maps";
import axios from 'axios';
import NavWrapper from './navwrapper.js';
class MapPage extends Component {
constructor(props) {
super(props);
this.state = {
markers: {
position: {
lat: 48.723627,
lng: 21.254199900000003,
},
key: Date.now(),
defaultAnimation: 2,
},
mapCenter: { lat: 48.723627, lng: 21.254199900000003 },
access_token: '',
address: '',
mapRef: null
}
}
componentWillMount() {
}
handleMapClick = this.handleMapClick.bind(this);
handleMapDrag = this.handleMapDrag.bind(this);
handleMapLoad = this.handleMapLoad.bind(this);
handleMapClick(event) {
let that = this;
this.setState({
markers: {
position: event.latLng,
defaultAnimation: 2,
key: Date.now()
},
mapCenter: event.latLng
});
}
handleAddressChange(latLngObject, address) {
console.log('addr: => '+address);
}
handleMapDrag() {
let mapRef = this._mapComponent;
console.log(mapRef.getCenter().lat()+'; '+mapRef.getCenter().lng());
}
handleMapLoad(map) {
this._mapComponent = map;
}
render() {
const GoogleMapWrapper = withGoogleMap(props => (
<GoogleMap
ref={props.onMapLoad}
defaultZoom={13}
defaultCenter={props.center}
onClick={props.onMapClick}
onDragEnd={props.onDragEnd}
>
<Marker {...props.markers} />
</GoogleMap>
));
return (
<div className="row-100">
<NavWrapper/>
<GoogleAddressAutocomplete addressChange={this.handleAddressChange.bind(this)} address={this.state.address}/>
<br />
<GoogleMapWrapper
containerElement={
<div style={{ height: `50vh` }} />
}
mapElement={
<div style={{ height: `50vh` }} />
}
onMapClick={this.handleMapClick}
onDragEnd={this.handleMapDrag}
onMapLoad={this.handleMapLoad}
markers={this.state.markers}
center={this.state.mapCenter}
/>
</div>
)
}
}
export default MapPage;
The actual center of the map is logged to the console in the handleMapDrag() function.
Instead of returning that, return this:
return (
<div className="row-100">
<NavWrapper/>
<GoogleAddressAutocomplete addressChange={this.handleAddressChange.bind(this)} address={this.state.address}/>
<br />
<GoogleMapWrapper
containerElement={
<div style={{ height: `50vh` }} />
}
mapElement={
<div style={{ height: `50vh` }} />
}
onMapClick={this.handleMapClick}
onMapDrag={this.handleMapDrag}
onMapLoad={this.handleMapLoad}
markers={this.state.markers}
center={this.state.mapCenter}
/>
</div>
)
You don't need to bind "this" to the methods again in the events, you just want to call them. You just need to bind 'this' to the methods in the constructor.
Pass this:
handleMapClick = this.handleMapClick.bind(this);
handleMapDrag = this.handleMapDrag.bind(this);
handleMapLoad = this.handleMapLoad.bind(this);
to the constructor.
Not quite sure, I didn't work many times with React, but i think those may be the problems.
Hope it helps.
did you try giving onDragStart to the GoogleMap Component?

Categories

Resources