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)
)
};