Skip to content

Commit 532816a

Browse files
committed
feat(GoogleMapLoader): introduce loader to manage React elements
* Closes #141 * Closes #133 BREAKING CHANGE: GoogleMap with props.containerProps is now deprecated. Use GoogleMapLoader with props.googleMapElement instead We also suggest switching to callback based ref so that you'll get the component instance when it is mounted. Before: ```js <GoogleMap containerProps={{ ...this.props, style: { height: "100%", }, }} ref="map" defaultZoom={3} defaultCenter={{lat: -25.363882, lng: 131.044922}} onClick={::this.handleMapClick}> {this.state.markers.map((marker, index) => { return ( <Marker {...marker} onRightclick={this.handleMarkerRightclick.bind(this, index)} /> ); })} </GoogleMap> ``` After: ```js <GoogleMapLoader containerElement={ <div {...this.props} style={{ height: "100%", }} /> } googleMapElement={ <GoogleMap ref={(map) => console.log(map)} defaultZoom={3} defaultCenter={{lat: -25.363882, lng: 131.044922}} onClick={::this.handleMapClick}> {this.state.markers.map((marker, index) => { return ( <Marker {...marker} onRightclick={this.handleMarkerRightclick.bind(this, index)} /> ); })} </GoogleMap> } /> ```
1 parent e21d803 commit 532816a

File tree

3 files changed

+126
-54
lines changed

3 files changed

+126
-54
lines changed

src/GoogleMap.js

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
} from "react";
66

77
import {
8-
findDOMNode,
9-
} from "react-dom";
8+
default as warning,
9+
} from "warning";
1010

1111
import {
1212
default as GoogleMapHolder,
@@ -15,40 +15,47 @@ import {
1515
mapEventPropTypes,
1616
} from "./creators/GoogleMapHolder";
1717

18+
import {
19+
default as GoogleMapLoader,
20+
} from "./GoogleMapLoader";
21+
22+
const USE_NEW_BEHAVIOR_TAG_NAME = `__new_behavior__`;
23+
1824
export default class GoogleMap extends Component {
1925
static propTypes = {
20-
containerTagName: PropTypes.string.isRequired,
21-
containerProps: PropTypes.object.isRequired,
26+
containerTagName: PropTypes.string,
27+
containerProps: PropTypes.object,
28+
map: PropTypes.object,
2229
// Uncontrolled default[props] - used only in componentDidMount
2330
...mapDefaultPropTypes,
2431
// Controlled [props] - used in componentDidMount/componentDidUpdate
2532
...mapControlledPropTypes,
2633
// Event [onEventName]
2734
...mapEventPropTypes,
28-
}
35+
};
2936

3037
// Public APIs
3138
//
3239
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map
3340
//
3441
// [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; }).filter(function(it){ return it.match(/^get/) && !it.match(/Map$/); })
35-
getBounds () { return this.state.map.getBounds(); }
42+
getBounds () { return (this.props.map || this.refs.delegate).getBounds(); }
3643

37-
getCenter () { return this.state.map.getCenter(); }
44+
getCenter () { return (this.props.map || this.refs.delegate).getCenter(); }
3845

39-
getDiv () { return this.state.map.getDiv(); }
46+
getDiv () { return (this.props.map || this.refs.delegate).getDiv(); }
4047

41-
getHeading () { return this.state.map.getHeading(); }
48+
getHeading () { return (this.props.map || this.refs.delegate).getHeading(); }
4249

43-
getMapTypeId () { return this.state.map.getMapTypeId(); }
50+
getMapTypeId () { return (this.props.map || this.refs.delegate).getMapTypeId(); }
4451

45-
getProjection () { return this.state.map.getProjection(); }
52+
getProjection () { return (this.props.map || this.refs.delegate).getProjection(); }
4653

47-
getStreetView () { return this.state.map.getStreetView(); }
54+
getStreetView () { return (this.props.map || this.refs.delegate).getStreetView(); }
4855

49-
getTilt () { return this.state.map.getTilt(); }
56+
getTilt () { return (this.props.map || this.refs.delegate).getTilt(); }
5057

51-
getZoom () { return this.state.map.getZoom(); }
58+
getZoom () { return (this.props.map || this.refs.delegate).getZoom(); }
5259
// END - Public APIs
5360
//
5461
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map
@@ -59,55 +66,50 @@ export default class GoogleMap extends Component {
5966
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map
6067
//
6168
// [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; }).filter(function(it){ return !it.match(/^get/) && !it.match(/^set/) && !it.match(/Map$/); })
62-
fitBounds (bounds) { return this.state.map.fitBounds(bounds); }
69+
fitBounds (bounds) { return (this.props.map || this.refs.delegate).fitBounds(bounds); }
6370

64-
panBy (x, y) { return this.state.map.panBy(x, y); }
71+
panBy (x, y) { return (this.props.map || this.refs.delegate).panBy(x, y); }
6572

66-
panTo (latLng) { return this.state.map.panTo(latLng); }
73+
panTo (latLng) { return (this.props.map || this.refs.delegate).panTo(latLng); }
6774

68-
panToBounds (latLngBounds) { return this.state.map.panToBounds(latLngBounds); }
75+
panToBounds (latLngBounds) { return (this.props.map || this.refs.delegate).panToBounds(latLngBounds); }
6976
// END - Public APIs - Use this carefully
7077
//
7178
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map
7279

73-
static defaultProps = {
74-
containerTagName: "div",
75-
containerProps: {},
76-
}
77-
78-
state = {
79-
}
80+
componentWillMount () {
81+
const {containerTagName} = this.props;
82+
const isUsingNewBehavior = USE_NEW_BEHAVIOR_TAG_NAME === containerTagName;
8083

81-
componentDidMount () {
82-
const domEl = findDOMNode(this);
83-
const {containerTagName, containerProps, children, ...mapProps} = this.props;
84-
// TODO: support asynchronous load of google.maps API at this level.
85-
//
86-
// Create google.maps.Map instance so that dom is initialized before
87-
// React's children creators.
88-
//
89-
const map = GoogleMapHolder._createMap(domEl, mapProps);
90-
this.setState({ map });
84+
warning(isUsingNewBehavior,
85+
`"GoogleMap" with containerTagName is deprecated now and will be removed in next major release (5.0.0).
86+
Use "GoogleMapLoader" instead. See https://github.com/tomchentw/react-google-maps/pull/157 for more details.`
87+
);
9188
}
9289

9390
render () {
94-
const {containerTagName, containerProps, children, ...mapProps} = this.props;
95-
const child = this.state.map ? (
96-
// Notice: implementation details
97-
//
98-
// In this state, the DOM of google.maps.Map is already initialized in
99-
// my innerHTML. Adding extra React components will not clean it
100-
// in current version*. It will use prepend to add DOM of
101-
// GoogleMapHolder and become a sibling of the DOM of google.maps.Map
102-
// Not sure this is subject to change
103-
//
104-
// *current version: 0.13.3, 0.14.2
105-
//
106-
<GoogleMapHolder map={this.state.map} {...mapProps}>
107-
{children}
108-
</GoogleMapHolder>
109-
) : undefined;
110-
111-
return React.createElement(containerTagName, containerProps, child);
91+
const {containerTagName, containerProps = {}, children, ...mapProps} = this.props;
92+
const isUsingNewBehavior = USE_NEW_BEHAVIOR_TAG_NAME === containerTagName;
93+
94+
if (isUsingNewBehavior) {
95+
return (
96+
<GoogleMapHolder {...mapProps}>
97+
{children}
98+
</GoogleMapHolder>
99+
);
100+
} else {//------------ Deprecated ------------
101+
const realContainerTagName = null == containerTagName ? `div` : containerTagName;
102+
103+
return (
104+
<GoogleMapLoader
105+
containerElement={React.createElement(realContainerTagName, containerProps)}
106+
googleMapElement={
107+
<GoogleMap ref="delegate" containerTagName={USE_NEW_BEHAVIOR_TAG_NAME} {...mapProps}>
108+
{children}
109+
</GoogleMap>
110+
}
111+
/>
112+
);
113+
}
112114
}
113115
}

src/GoogleMapLoader.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
default as React,
3+
PropTypes,
4+
Component,
5+
} from "react";
6+
7+
import {
8+
default as propTypesElementOfType,
9+
} from "react-prop-types-element-of-type";
10+
11+
import {
12+
default as GoogleMapHolder,
13+
} from "./creators/GoogleMapHolder";
14+
15+
const USE_NEW_BEHAVIOR_TAG_NAME = `__new_behavior__`;/* CIRCULAR_DEPENDENCY */
16+
17+
export default class GoogleMapLoader extends Component {
18+
static propTypes = {
19+
containerElement: PropTypes.node.isRequired,
20+
googleMapElement: PropTypes.element.isRequired,/* CIRCULAR_DEPENDENCY. Uncomment when 5.0.0 comes: propTypesElementOfType(GoogleMap).isRequired, */
21+
};
22+
23+
static defaultProps = {
24+
containerElement: (<div />),
25+
};
26+
27+
state = {
28+
map: null,
29+
};
30+
31+
mountGoogleMap (domEl) {
32+
if (this.state.map) {
33+
return;
34+
}
35+
const {children, ...mapProps} = this.props.googleMapElement.props;
36+
//
37+
// Create google.maps.Map instance so that dom is initialized before
38+
// React's children creators.
39+
//
40+
const map = GoogleMapHolder._createMap(domEl, mapProps);
41+
this.setState({ map });
42+
}
43+
44+
renderChild () {
45+
if (this.state.map) {
46+
// Notice: implementation details
47+
//
48+
// In this state, the DOM of google.maps.Map is already initialized in
49+
// my innerHTML. Adding extra React components will not clean it
50+
// in current version*. It will use prepend to add DOM of
51+
// GoogleMapHolder and become a sibling of the DOM of google.maps.Map
52+
// Not sure this is subject to change
53+
//
54+
// *current version: 0.13.3, 0.14.2
55+
//
56+
return React.cloneElement(this.props.googleMapElement, {
57+
map: this.state.map,
58+
//------------ Deprecated ------------
59+
containerTagName: USE_NEW_BEHAVIOR_TAG_NAME,
60+
});
61+
}
62+
}
63+
64+
render () {
65+
return React.cloneElement(this.props.containerElement, {
66+
ref: ::this.mountGoogleMap,
67+
}, this.renderChild());
68+
}
69+
}

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
export {default as GoogleMap} from "./GoogleMap";
1+
export {default as GoogleMapLoader} from "./GoogleMapLoader";
22

3+
export {default as GoogleMap} from "./GoogleMap";
34
export {default as Circle} from "./Circle";
45
export {default as DirectionsRenderer} from "./DirectionsRenderer";
56
export {default as DrawingManager} from "./DrawingManager";

0 commit comments

Comments
 (0)