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?
Related
Been working with react and still trying to grasp all the concepts. Could use help to understand how I can get this to work. I want to pass an array as props to another react component to map over. Can someone point out what I am doing wrong here? I can map through the array as a function but not inside render:
App.js
import React, { Component } from 'react';
import Leaf from './components/Leaf';
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
height: "100vh",
width: "100vw",
latitude: 40.7128,
longitude: -74.0060,
zoom: 10
},
latitude: 40.7128,
longitude: -74.0060,
zoom: 10,
stations: [],
selectedStation: null,
userLocation: {}
};
}
componentDidMount() {
fetch('https://gbfs.citibikenyc.com/gbfs/en/station_information.json')
.then(res => res.json())
.then(res=>
this.setState({stations: res}))
}
checkData=()=>{
console.log(this.state.stations)
this.state.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
return (
<div>
<button onClick={this.checkData}>click me</button>
<Leaf
viewport={this.state.viewport}
stations={this.state.stations}/>
</div>
);
}
}
export default App;
leaf.js
import React, {Component} from 'react';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
import { Button } from 'react-bootstrap';
class leaf extends Component {
checkData=()=>{
this.props.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
const markers = this.props.stations.data.stations.map((station) =>
<Marker
position={[station.lat, station.lon]}
onClick={this.markerClick.bind(this,station)}>
<Popup>
</Popup>
</Marker>
);
const position = [this.props.viewport.latitude, this.props.viewport.longitude]
//const position = [40.7484, -73.9857]
return (
<div>
<button onClick={this.checkData}>check props</button>
<Map center={position} zoom={14}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{markers}
<Marker position={position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</Map>
</div>
);
}
}
export default leaf;
In the past I've done something like :
const Search = ( props ) => {
return (
)
}
But I am looking to understand how to make this work using class leaf extends Component . Appreciate the help.
Data Structure for Reference
{
last_updated: 1572631066,
ttl: 10,
data: {
stations: [
{
station_id: "237",
external_id: "66db3c29-0aca-11e7-82f6-3863bb44ef7c",
name: "E 11 St & 2 Ave",
short_name: "5746.04",
lat: 40.73047309,
lon: -73.98672378,
region_id: 71,
rental_methods: [
"CREDITCARD",
"KEY"
],
capacity: 39,
rental_url: "http://app.citibikenyc.com/S6Lr/IBV092JufD?station_id=237",
electric_bike_surcharge_waiver: false,
eightd_has_key_dispenser: false,
eightd_station_services: [
{
id: "e73b6bfb-961f-432c-a61b-8e94c42a1fba",
service_type: "ATTENDED_SERVICE",
bikes_availability: "UNLIMITED",
docks_availability: "NONE",
name: "Valet Service",
description: "Citi Bike Station Valet attendant service available",
schedule_description: "",
link_for_more_info: "https://www.citibikenyc.com/valet"
}
],
has_kiosk: true
},
{
station_id: "281",
external_id: "66db5fae-0aca-11e7-82f6-3863bb44ef7c",
name: "Grand Army Plaza & Central Park S",
short_name: "6839.10",
lat: 40.7643971,
lon: -73.97371465,
region_id: 71,
rental_methods: [
"CREDITCARD",
"KEY"
],
capacity: 66,
rental_url: "http://app.citibikenyc.com/S6Lr/IBV092JufD?station_id=281",
electric_bike_surcharge_waiver: false,
eightd_has_key_dispenser: true,
eightd_station_services: [
{
id: "32461582-cd1e-4ecf-a5ea-563593fa7009",
service_type: "ATTENDED_SERVICE",
bikes_availability: "UNLIMITED",
docks_availability: "NONE",
name: "Valet Service",
description: "Citi Bike Valet Attendant Service Available",
schedule_description: "",
link_for_more_info: "https://www.citibikenyc.com/valet"
}
],
has_kiosk: true
}
]
}
}
Attempts
So here I changed the const markers to mirror the conosle.log from checkData:
const markers = this.props.stations.data.stations.map((station) =>
<Marker
position={[station.lat, station.lon]}
onClick={this.markerClick.bind(this,station)}>
<Popup>
</Popup>
</Marker>
);
I get the following error:
TypeError: Cannot read property 'stations' of undefined
When I remove the markers variable and click checkData, notice that it has not issue mapping over and console logging the object:
I'd doublecheck that you're accessing the correct data properties, as in your function you're calling this.props.stations.data.stations.map, but in your render you're calling this.props.stations.data.map, so one must be incorrect.
Also, class components should be PascalCase, i.e. capitalized.
Camilo was correct and I needed to make sure data was available for render. Below is the working code:
App.js
//import logo from './logo.svg';
//import './App.css';
import React, { Component } from 'react';
import Leaf from './components/Leaf';
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
height: "100vh",
width: "100vw",
latitude: 40.7128,
longitude: -74.0060,
zoom: 10
},
latitude: 40.7128,
longitude: -74.0060,
zoom: 10,
stations: [],
showStations: false,
selectedStation: null,
userLocation: {}
};
}
componentDidMount() {
const request = async()=> {
await fetch('https://gbfs.citibikenyc.com/gbfs/en/station_information.json')
.then(res => res.json())
.then(res=>
this.setState({stations: res, showStations: true}))
}
request();
}
checkData=()=>{
console.log(this.state.stations)
this.state.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
return (
<div>
<button onClick={this.checkData}>click me</button>
<Leaf
viewport={this.state.viewport}
stations={this.state.stations}
showStations={this.state.showStations}/>
</div>
);
}
}
export default App;
Leaf.js
import React, {Component} from 'react';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
//import './leaf.css'
//import InfoBox from './InfoBox';
import { Button } from 'react-bootstrap';
//import Search from './Search';
//import Match from './Match';
//import Modal from './Modal';
//import L from 'leaflet';
//import Routing from "./RoutingMachine";
//import { Row, Col, Grid, Container } from 'react-bootstrap';
//import ErrorBoundary from '../ErrorBoundary/ErrorBoundary'
class Leaf extends Component {
checkData=()=>{
this.props.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
let markers =null;
if(this.props.showStations) {
markers = (
<div>
{
this.props.stations.data.stations.map((station) =>
<Marker
position={[station.lat, station.lon]}>
</Marker>
)
}
</div>
)
}
const position = [this.props.viewport.latitude, this.props.viewport.longitude]
//const position = [40.7484, -73.9857]
return (
<div>
<button onClick={this.checkData}>check props</button>
<Map center={position} zoom={14}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{markers}
<Marker position={position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</Map>
</div>
);
}
}
export default Leaf;
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/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.
What I am trying to do:
Be able to click a marker and have it show the InfoWindow and then be able to click the marker again to close it or just close the pop up window manually. Also want to be able to open multiple markers at once.
What works:
I can click to open InfoWindow and click again to close the window. If I close one marker then I can open another one.
What doesn't work:
If one marker is already open, and I try to click another to open it I get:
Target container is not a DOM element
The above error occurred in the <InfoWindow> component:
This happens only when I click another marker if another marker is already open. I will post the code below. I have seperated out the MapComponent into a separate file.
MapComponent:
import React from 'react';
import { compose, withProps } from 'recompose';
import { withScriptjs, withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps';
import config from '../config';
import { MarkerInfo } from '../components/view';
export const MapComponent = compose(
withProps({
googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${config.GOOGLE_API_KEY}&v=3.exp&libraries=geometry,drawing,places`,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `500px` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={8} center={props.center}>
{props.isMarkerShown &&
props.center && (
<Marker position={props.center} title={"User's Location"} onClick={props.onHomeMarkerClick}>
<InfoWindow>
<div>User's Location</div>
</InfoWindow>
</Marker>
)}
{props.markers.map((marker, i) => {
const onClick = () => props.onMarkerClick(marker);
const onCloseClick = () => props.onCloseClick(marker);
return (
<Marker key={i} position={marker.position} title={marker.title} onClick={onClick}>
{marker.showInfo && (
<InfoWindow onCloseClick={onCloseClick}>
<div>
<MarkerInfo marker={marker} />
</div>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
));
React Component Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import geolib from 'geolib';
import { geolocated } from 'react-geolocated';
import axios from 'axios';
import actions from '../../actions';
import { MapComponent, Geocode } from '../../utils';
class GhostMap extends Component {
constructor(props) {
super(props);
this.state = {
isMarkerShown: true,
currentLocation: null,
radius: 100,
markers: []
};
this.handleMarkerClick = this.handleMarkerClick.bind(this);
this.handleCloseClick = this.handleCloseClick.bind(this);
this.handleHomeMarkerClick = this.handleHomeMarkerClick.bind(this);
this.createMarkersWithinRadius = this.createMarkersWithinRadius.bind(this);
this.updateRadius = this.updateRadius.bind(this);
}
componentDidMount() {
if (this.props.posts.all) {
return;
}
console.log(this.props);
this.props
.fetchPosts()
.then(data => {
return data;
})
.catch(err => {
console.log(err);
});
}
componentWillReceiveProps(props) {
if (props.coords) {
this.setState({
currentLocation: {
lat: props.coords.latitude,
lng: props.coords.longitude
}
});
this.createMarkersWithinRadius(
props.posts.all,
this.state.radius,
props.coords.latitude,
props.coords.longitude
)
.then(data => {
this.setState({
markers: data
});
return data;
})
.catch(err => {
console.log(err);
});
} else {
axios
.get('http://ip-api.com/json')
.then(response => {
this.setState({
currentLocation: {
lat: response.data.lat,
lng: response.data.lon
}
});
return this.createMarkersWithinRadius(
props.posts.all,
this.state.radius,
response.data.lat,
response.data.lon
);
})
.then(data => {
this.setState({
markers: data
});
})
.catch(err => {
console.log(err);
});
}
}
createMarkersWithinRadius(posts, radius, lat, lng) {
return new Promise((resolve, reject) => {
const markers = [];
const currentLocation = {
latitude: lat,
longitude: lng
};
posts.map(post => {
let postGeolocation = {};
Geocode(post.address, post.city, post.state, post.zipCode)
.then(response => {
postGeolocation.lat = response.lat;
postGeolocation.lng = response.lng;
const distanceArr = geolib.orderByDistance(currentLocation, [postGeolocation]);
const miles = (distanceArr[0].distance / 1609.34).toFixed(2);
if (miles <= radius) {
markers.push({
id: post.id,
position: postGeolocation,
title: post.title,
description: post.text,
image: post.image,
showInfo: false
});
}
resolve(markers);
})
.catch(err => {
reject(err);
});
});
});
}
handleMarkerClick(targetMarker) {
this.setState({
markers: this.state.markers.map(
marker =>
marker.id === targetMarker.id ? { ...marker, showInfo: !marker.showInfo } : marker
)
});
}
handleCloseClick(targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker._id === targetMarker._id) {
return {
...marker,
showInfo: false
};
}
return marker;
})
});
}
handleHomeMarkerClick() {
this.setState({ isMarkerShown: false });
}
updateRadius(event) {
const radius = event.target.value;
const posts = this.props.posts.all;
const { lat, lng } = this.state.currentLocation;
if (typeof radius !== 'number') {
alert('Please put in a number');
return;
}
if (this.props.posts.all && this.state.currentLocation) {
this.createMarkersWithinRadius(this.props.posts.all, radius, lat, lng);
}
}
render() {
if (this.state.markers.length === 0) {
return <div>Loading...</div>;
}
return (
<div className="row">
<div className="col-sm-4">
<div className="form-group">
<label htmlFor="text">Radius</label>
<input
onChange={this.updateRadius}
className="form-control"
id="radius"
type="Number"
placeholder="Radius"
/>
</div>
</div>
<div className="col-sm-12">
<MapComponent
onMarkerClick={this.handleMarkerClick}
isMarkerShown={this.state.isMarkerShown}
onHomeMarkerClick={this.handleHomeMarkerClick}
center={this.state.currentLocation}
markers={this.state.markers}
onCloseClick={this.handleCloseClick}
/>
</div>
</div>
);
}
}
const stateToProps = state => {
return {
posts: state.post
};
};
const dispatchToProps = dispatch => {
return {
fetchPosts: () => dispatch(actions.fetchPosts())
};
};
const loadData = store => {
return store.dispatch(actions.fetchPosts());
};
export default {
loadData: loadData,
component: connect(stateToProps, dispatchToProps)(
geolocated({
positionOptions: {
enableHighAccuracy: false
},
userDecisionTimeout: 5000
})(GhostMap)
)
};
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.