Skip to content

Instantly share code, notes, and snippets.

@cedricdelpoux
Created August 9, 2016 15:21
Show Gist options
  • Save cedricdelpoux/dcd36df35763347c2f0fa593167968b0 to your computer and use it in GitHub Desktop.
Save cedricdelpoux/dcd36df35763347c2f0fa593167968b0 to your computer and use it in GitHub Desktop.
React Google Map
import React, {Component, PropTypes} from "react"
import placesLoader from "./placesLoader"
import placesShape from "./placesShape"
import styles from "./GoogleMap.css"
import iconMarker from "./iconMarker.png"
import iconTrash from "./iconTrash.png"
const {arrayOf, func, number, shape, string} = PropTypes
const BOUNDS_OFFSET = 0.002 // If there is a lonely marker, map will extends bounds of BOUNDS_OFFSET km
const DEFAULT_LAT = 43.604305 // Toulouse
const DEFAULT_LNG = 1.443999 // Toulouse
class GoogleMap extends Component {
constructor() {
super()
this.state = {
map: null,
markers: new Map(),
}
}
componentDidMount() {
const {googleMaps, zoom} = this.props
const map = new googleMaps.Map(this.map, {
zoom,
center: new googleMaps.LatLng(DEFAULT_LAT, DEFAULT_LNG),
panControl: false,
zoomControl: true,
mapTypeControl: false,
scaleControl: false,
streetViewControl: false,
overviewMapControl: false,
})
this.setState({map}, () => this.initMarkers()) // eslint-disable-line
}
componentWillReceiveProps(nextProps) {
const newMarkers = nextProps.coordinates.some(coordinate => !this.state.markers.has(this.getMarkerId(coordinate)))
if (newMarkers) {
this.updateMarkers(nextProps.coordinates)
}
}
shouldComponentUpdate(nextProps) {
return this.props.coordinates.length !== nextProps.coordinates.length
}
initMarkers() {
this.addNewMarkers(this.props.coordinates)
}
updateMarkers(coordinates) {
this.addNewMarkers(coordinates)
if (this.props.onChange) {
this.props.onChange({
coordinates: this.getNewCoordinates(),
zoom: this.state.map.getZoom(),
})
}
}
addNewMarkers(coordinates) {
const {markers} = this.state
coordinates.forEach((coordinate) => {
const markerId = this.getMarkerId(coordinate)
if (!markers.has(markerId)) {
markers.set(markerId, this.addMarker(markerId, coordinate))
}
})
this.setState({markers})
}
getMarkerId(coordinate) {
return coordinate.latitude + "_" + coordinate.longitude
}
getNewCoordinates() {
return Array.from(this.state.markers.values()).map((marker) => {
const position = marker.getPosition()
return {
description: marker.description,
latitude: position.lat(),
longitude: position.lng(),
title: marker.getTitle(),
}
})
}
addMarker(markerId, coordinate) {
const {googleMaps} = this.props
const {map} = this.state
const marker = new googleMaps.Marker({
map,
animation: googleMaps.Animation.DROP,
position: new googleMaps.LatLng(coordinate.latitude, coordinate.longitude),
title: coordinate.title,
description: coordinate.description,
icon: iconMarker,
})
googleMaps.event.addListener(marker, "mouseover", () => {
marker.setIcon(iconTrash)
})
googleMaps.event.addListener(marker, "mouseout", () => {
marker.setIcon(iconMarker)
})
googleMaps.event.addListener(marker, "click", () => {
this.removeMarker(markerId)
})
return marker
}
removeMarker(markerId) {
const {map, markers} = this.state
const marker = markers.get(markerId)
marker.setMap(null)
markers.delete(markerId)
if (this.props.onChange) {
this.props.onChange({
coordinates: this.getNewCoordinates(),
zoom: map.getZoom(),
})
}
}
fitBounds() {
const {googleMaps} = this.props
const {map, markers} = this.state
if (!map || markers.size === 0) {
return
}
const bounds = Array.from(markers.values()).reduce((b, marker) => b.extend(marker.getPosition()), new googleMaps.LatLngBounds())
const center = bounds.getCenter()
bounds
.extend(new googleMaps.LatLng(center.lat() + BOUNDS_OFFSET, center.lng() + BOUNDS_OFFSET))
.extend(new googleMaps.LatLng(center.lat() - BOUNDS_OFFSET, center.lng() - BOUNDS_OFFSET))
map.setCenter(center)
map.fitBounds(bounds)
}
render() {
this.fitBounds()
return (
<div ref={ref => this.ref_map = ref} className={styles.map} />
)
}
}
GoogleMap.propTypes = {
coordinates: arrayOf(shape({
description: string,
latitude: number.isRequired,
longitude: number.isRequired,
title: string.isRequired,
})),
googleMaps: placesShape,
onChange: func,
zoom: number.isRequired,
}
GoogleMap.defaultProps = {
coordinates: [],
googleMaps: null,
zoom: 8,
}
export default placesLoader(GoogleMap)
import React, {Component} from "react"
import load from "little-loader"
import {GOOGLE_MAP_PLACES_API} from "constants/Apis"
const NOT_LOADED = 0
const LOADING = 1
const LOADED = 2
const queue = []
let state = NOT_LOADED
let sdk
function useGoogleMapSdk(callback) {
if (state === LOADED) {
callback(sdk)
} else if (state === LOADING) {
queue.push(callback)
} else if (window.google != null && window.google.maps != null) {
state = LOADED
sdk = window.google.maps
callback(sdk)
} else {
state = LOADING
queue.push(callback)
load(GOOGLE_MAP_PLACES_API, (err) => {
if (err) {
throw new Error("Unable to load Google Map SDK")
}
state = LOADED
sdk = window.google.maps
while (queue.length > 0) {
queue.pop()(sdk)
}
})
}
}
export default (TargetComponent) => (
class extends Component {
constructor() {
super()
this.state = {
googleMaps: null,
}
}
componentWillMount() {
useGoogleMapSdk(googleMaps => this.setState({googleMaps}))
}
render() {
const {googleMaps} = this.state
return googleMaps
? <TargetComponent googleMaps={googleMaps} {...this.props} />
: null
}
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment