Seems this library was written to be used with just React. The documentation doesn't say it depends on recompose. I've got it working without recompose.
Is there a reason this react package is documented with recompose only?
Unwinding what recompose was doing so I could write it in just React(es6/7) was painful and I am sure other don't want to go through that too. Though i'd ask in case there was a something that I was missing.
Thanks.
You can totally use vanilla React without recompose. The documentation is written with recompose just for simplicity.
Just notice three things when using it:
withGoogleMap HOCwithScriptjs HOC, or you'll have to load Google Maps JavaScript API manually in your HTML <head> tagcontainerElement/loadingElement/mapElement/googleMapURLFrom that, it should be:
const MyMapComponent = withScriptjs(withGoogleMap(props => {
return <GoogleMap><Marker /></GoogleMap>
}))
<MyMapComponent
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
Could you show an an example using class X extends React.Component syntax?
@sangel10 you just use the MyMapComponent in your class X extends React.Component's render function. It's totally up to you to implement state changes in any way (redux, react local state, recompose……etc)
That works for simple examples but what about an example using nested components?
e.g. https://tomchentw.github.io/react-google-maps/#markerclusterer
@sangel10 I updated the example for you. Is it clear enough now?
https://tomchentw.github.io/react-google-maps/#!/MarkerClusterer
You can also create a wrapper of the form:
_GoogleMapsWrapper.js_
import React from 'react';
import { GoogleMap,withGoogleMap,withScriptjs } from 'react-google-maps';
export default const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
return <GoogleMap {...props} ref={props.onMapMounted}>{props.children}</GoogleMap>
}));
Then use it as GoogleMap in the example, without any recompose stuff. You can pass props and nest components exactly like for GoogleMap:
_main.js_
import React from 'react';
import GoogleMapsWrapper from './GoogleMapsWrapper.js';
import { Marker } from 'react-google-maps';
import MarkerClusterer from "react-google-maps/lib/components/addons/MarkerClusterer";
class DemoApp extends React.Component {
componentWillMount() {
this.setState({ markers: [] })
}
componentDidMount() {
const url = [
// Length issue
`https://gist.githubusercontent.com`,
`/farrrr/dfda7dd7fccfec5474d3`,
`/raw/758852bbc1979f6c4522ab4e92d1c92cba8fb0dc/data.json`
].join("")
fetch(url)
.then(res => res.json())
.then(data => {
this.setState({ markers: data.photos });
});
}
render () {
return (
<GoogleMapsWrapper
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }}
defaultZoom={3}
defaultCenter={{ lat: 25.0391667, lng: 121.525 }}>
<MarkerClusterer
averageCenter
enableRetinaIcons
gridSize={60}>
{this.state.markers.map(marker => (
<Marker
key={marker.photo_id}
position={{ lat: marker.latitude, lng: marker.longitude }}
/>
))}
</MarkerClusterer>
</GoogleMapsWrapper>
);
}
}
@MonsieurV your mapElement prop is missing a />}. And the GoogleMapsWrapper throws an error. Apparently the wrapper/export should look like this:
const GoogleMapsWrapper = withScriptjs(withGoogleMap ...
export default GoogleMapsWrapper;
After these changes @MonsieurV's code works for me. Does this look good to everyone? Would be very helpful to see something like this in the docs.
Ran into an issue with that ES6 React code. When I attempted to add an onBoundsChanged callback the getBounds and getCenter functions threw an error. Eventually found that I was passing in the onMapMounted prop incorrectly, decided to post here for posterity.
GoogleMapsWrapper.js:
import React from 'react';
import { GoogleMap,withGoogleMap,withScriptjs } from 'react-google-maps';
const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
return <GoogleMap {...props} ref={props.onMapMounted}>{props.children}</GoogleMap>
}));
export default GoogleMapsWrapper;
map.jsx
```
import GoogleMapsWrapper from './GoogleMapsWrapper.js';
import { Marker } from 'react-google-maps';
import MarkerClusterer from "react-google-maps/lib/components/addons/MarkerClusterer";
export default class MapSearch extends React.Component {
componentWillMount() {
let refs = {};
this.setState({
markers: [],
onMapMounted: map => {
refs.map = map;
},
onBoundsChanged: () => {
console.log(refs.map) // (not a Container, a Map) Map {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}
this.setState({
bounds: refs.map.getBounds(),
center: refs.map.getCenter()
});
}
});
}
render() {
return (
<GoogleMapsWrapper
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyCMh8-5D3mJSXspmJrhSTtt0ToGiA-JLBc&libraries=geometry,drawing,places" // libraries=geometry,drawing,places
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
defaultZoom={12}
defaultCenter={{ lat: 37.7749, lng: -122.4194 }}
onMapMounted={this.state.onMapMounted}
onBoundsChanged={this.state.onBoundsChanged}
>
<MarkerClusterer
averageCenter
enableRetinaIcons
gridSize={60}
>
{this.state.markers.map(marker => (
<Marker
key={marker.photo_id}
position={{ lat: marker.latitude, lng: marker.longitude }}
/>
))}
</MarkerClusterer>
</GoogleMapsWrapper>
);
}
}
@JarLowrey yeah, because ref callback needs to be assigned to the <GoogleMap> element, not the <GoogleMapsWrapper> one.
For anyone reading this in the future, please NEVER put functions in the state like @JarLowrey did, this is not a good practice. The state is for keeping internal data relative to the rendering of your component and nothing else
PS: As shown in the code, the ref can be null at the componentDidMount phase but it's because that the <GoogleMapsWrapper /> loses the hand over the lifecycle phase of the <GoogleMap />, but will be defined a moment after the mount. I've never seen a case like this, it may happen because of withScriptjs & withGoogleMap.
Use this safe way to set the ref (with the help of @MonsieurV's wrapper) edited multiple times to include a working PoC:
import React from 'react';
import {GoogleMap, Marker, withGoogleMap, withScriptjs} from 'react-google-maps';
import MarkerClusterer from "react-google-maps/lib/components/addons/MarkerClusterer";
const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
const {onMapMounted, ...otherProps} = props;
return <GoogleMap {...otherProps} ref={c => {
onMapMounted && onMapMounted(c)
}}>{props.children}</GoogleMap>
}));
export default class MapPage extends React.Component {
state = {
markers: [],
};
componentDidMount() {
console.log('Mounted @ ' + Date.now());
const url = "https://gist.githubusercontent.com/farrrr/dfda7dd7fccfec5474d3/raw/758852bbc1979f6c4522ab4e92d1c92cba8fb0dc/data.json";
fetch(url)
.then(res => res.json())
.then(data => {
this.setState({markers: data.photos});
});
}
_mapRef = null;
_handleMapMounted = (c) => {
if (!c || this._mapRef) return;
this._mapRef = c;
console.log('Ref set later @ ' + Date.now());
};
_handleBoundsChanged = () => {
if (!this._mapRef) return;
const center = this._mapRef.getCenter();
const bounds = this._mapRef.getBounds();
// console.log(center, bounds);
};
render() {
return (
<GoogleMapsWrapper
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{height: `100%`}}/>}
containerElement={<div style={{height: `100%`}}/>}
mapElement={<div style={{height: `100%`}}/>}
defaultZoom={3}
defaultCenter={{lat: 25.0391667, lng: 121.525}}
onMapMounted={this._handleMapMounted}
onBoundsChanged={this._handleBoundsChanged}>
<MarkerClusterer
averageCenter
enableRetinaIcons
gridSize={60}>
{this.state.markers.map(marker => (
<Marker
key={marker.photo_id}
position={{lat: marker.latitude, lng: marker.longitude}}
/>
))}
</MarkerClusterer>
</GoogleMapsWrapper>
)
}
}
PS2: Other bad practices spotted:
this.setState in componentWillMountcomponentWillMount, refs are not defined yet you have to wait until in componentDidMountsetState to put a function in the state that sets the state ?@VinceBT I copy/pasted from the code in the documentation. If this is poor design, which I can understand, perhaps the react-google-maps docs should be updated?
Always take some steps back with React since it is a growing framework, there are some ideologies that have grown around it, some are good, some are bad, it's your job to not spread bad practices. Every time I see your code I notice a new one and I update my post to include it.
Could you indicate the precises blocks with the bad practices I mentionned that you copied ? If there are, it would be a great idea to submit a PR then.
Map with a SearchBox, Standalone SearchBox has a function in the state that sets state.
Map with a MarkerClusterer calls setState in componentWillMount.
Standalone SearchBox and Map with a SearchBox use refs in componentWillMount.
Well that's a pretty bad doc then.
I wouldn't believe any piece of code/documentation that's written by the same guy that makes a library that need recompose for obscure reasons, my two cents. Take the good practices from the React documentation and nowhere else.
@VinceBT I used your PoC code and just copy pasted it onto a new file and imported the MapPage component in my index.js. I see the markers being imported and the console logs for the refs, but the app screen is blank. Is there something I am missing?
My React and react-dom versions are 16.4.0 each.
where can i get "google" ?
const DirectionsService = new google.maps.DirectionsService()
My component:
class Map extends Component {
constructor (props) {
super(props)
this.state = {
searchText: ''
}
}
componentDidMount () {
// google ?
const DirectionsService = new google.maps.DirectionsService()
}
render () {
const { location, directions } = this.props
if (!location) {
return null
}
return (
<GoogleMap
defaultZoom={18}
defaultCenter={location}
defaultOptions={MAP_OPTIONS}
center={location}
>
{directions && <DirectionsRenderer directions={directions} />}
<Marker
position={location}
/>
</GoogleMap>
)
}
}
export default withScriptjs(withGoogleMap(Map))
@hungdt-ibl
Grab it from window (window.google)
Hope this helps
@VinceBT thanks for the code snippet above, huge help! Was struggling with this for a while.
For posterity, if anyone wants a more fleshed-out example...
import React, { Component } from "react";
// needing for panning and zooming the map to a specific point
import { MAP } from "react-google-maps/lib/constants";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker
} from "react-google-maps";
const Map = withScriptjs(
withGoogleMap(props => (
<GoogleMap
ref={props.onMapMounted}
defaultZoom={8}
defaultCenter={{ lat: 38, lng: -77 }}
>
{/* display markers, polylines, whatever inside the map */}
{props.children}
</GoogleMap>
))
);
export default class MyMap extends Component {
constructor(props) {
super(props);
this.zoomToPoint = this.zoomToPoint.bind(this);
this.onMapMounted = this.onMapMounted.bind(this);
this.map = React.createRef();
this.mapObject = React.createRef();
}
onMapMounted(ref) {
this.map = ref;
// needing for panning and zooming the map to a specific point
this.mapObject = ref ? ref.context[MAP] : null;
}
// used to pan to a google.maps.LatLng
zoomToPoint(position) {
if (this.map && this.mapObject) {
this.map.panTo(position);
this.mapObject.setZoom(17);
}
}
render() {
return (
<Map
onMapMounted={this.onMapMounted}
googleMapURL="<YOUR GOOGLE MAPS URL>"
loadingElement={<div style={{ height: `100%`, width: `100%` }} />}
containerElement={
<div
id="map-container"
style={{ height: `100%`, width: `100%` }}
/>
}
mapElement={
<div id="map" style={{ height: `100%`, width: `100%` }} />
}
>
{/* render markers or polylines etc in here like you would inside GoogleMap */}
{markers.map(m => (
<Marker
key={m.id}
position={{
lat: m.latitude,
lng: m.longitude
}}
/>
)}
</Map>
);
}
}
Most helpful comment
For anyone reading this in the future, please NEVER put functions in the state like @JarLowrey did, this is not a good practice. The state is for keeping internal data relative to the rendering of your component and nothing else
PS: As shown in the code, the ref can be
nullat thecomponentDidMountphase but it's because that the<GoogleMapsWrapper />loses the hand over the lifecycle phase of the<GoogleMap />, but will be defined a moment after the mount. I've never seen a case like this, it may happen because of withScriptjs & withGoogleMap.Use this safe way to set the ref (with the help of @MonsieurV's wrapper) edited multiple times to include a working PoC:
PS2: Other bad practices spotted:
this.setStateincomponentWillMountcomponentWillMount, refs are not defined yet you have to wait until incomponentDidMountsetStateto put a function in the state that sets the state ?