diff --git a/src/GoogleMap.js b/src/GoogleMap.js index 4665d3fc..a5b5450c 100644 --- a/src/GoogleMap.js +++ b/src/GoogleMap.js @@ -5,8 +5,8 @@ import { } from "react"; import { - findDOMNode, -} from "react-dom"; + default as warning, +} from "warning"; import { default as GoogleMapHolder, @@ -15,40 +15,47 @@ import { mapEventPropTypes, } from "./creators/GoogleMapHolder"; +import { + default as GoogleMapLoader, +} from "./GoogleMapLoader"; + +const USE_NEW_BEHAVIOR_TAG_NAME = `__new_behavior__`; + export default class GoogleMap extends Component { static propTypes = { - containerTagName: PropTypes.string.isRequired, - containerProps: PropTypes.object.isRequired, + containerTagName: PropTypes.string, + containerProps: PropTypes.object, + map: PropTypes.object, // Uncontrolled default[props] - used only in componentDidMount ...mapDefaultPropTypes, // Controlled [props] - used in componentDidMount/componentDidUpdate ...mapControlledPropTypes, // Event [onEventName] ...mapEventPropTypes, - } + }; // Public APIs // // https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map // // [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; }).filter(function(it){ return it.match(/^get/) && !it.match(/Map$/); }) - getBounds () { return this.state.map.getBounds(); } + getBounds () { return (this.props.map || this.refs.delegate).getBounds(); } - getCenter () { return this.state.map.getCenter(); } + getCenter () { return (this.props.map || this.refs.delegate).getCenter(); } - getDiv () { return this.state.map.getDiv(); } + getDiv () { return (this.props.map || this.refs.delegate).getDiv(); } - getHeading () { return this.state.map.getHeading(); } + getHeading () { return (this.props.map || this.refs.delegate).getHeading(); } - getMapTypeId () { return this.state.map.getMapTypeId(); } + getMapTypeId () { return (this.props.map || this.refs.delegate).getMapTypeId(); } - getProjection () { return this.state.map.getProjection(); } + getProjection () { return (this.props.map || this.refs.delegate).getProjection(); } - getStreetView () { return this.state.map.getStreetView(); } + getStreetView () { return (this.props.map || this.refs.delegate).getStreetView(); } - getTilt () { return this.state.map.getTilt(); } + getTilt () { return (this.props.map || this.refs.delegate).getTilt(); } - getZoom () { return this.state.map.getZoom(); } + getZoom () { return (this.props.map || this.refs.delegate).getZoom(); } // END - Public APIs // // https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map @@ -59,55 +66,50 @@ export default class GoogleMap extends Component { // https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map // // [].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$/); }) - fitBounds (bounds) { return this.state.map.fitBounds(bounds); } + fitBounds (bounds) { return (this.props.map || this.refs.delegate).fitBounds(bounds); } - panBy (x, y) { return this.state.map.panBy(x, y); } + panBy (x, y) { return (this.props.map || this.refs.delegate).panBy(x, y); } - panTo (latLng) { return this.state.map.panTo(latLng); } + panTo (latLng) { return (this.props.map || this.refs.delegate).panTo(latLng); } - panToBounds (latLngBounds) { return this.state.map.panToBounds(latLngBounds); } + panToBounds (latLngBounds) { return (this.props.map || this.refs.delegate).panToBounds(latLngBounds); } // END - Public APIs - Use this carefully // // https://developers.google.com/maps/documentation/javascript/3.exp/reference#Map - static defaultProps = { - containerTagName: "div", - containerProps: {}, - } - - state = { - } + componentWillMount () { + const {containerTagName} = this.props; + const isUsingNewBehavior = USE_NEW_BEHAVIOR_TAG_NAME === containerTagName; - componentDidMount () { - const domEl = findDOMNode(this); - const {containerTagName, containerProps, children, ...mapProps} = this.props; - // TODO: support asynchronous load of google.maps API at this level. - // - // Create google.maps.Map instance so that dom is initialized before - // React's children creators. - // - const map = GoogleMapHolder._createMap(domEl, mapProps); - this.setState({ map }); + warning(isUsingNewBehavior, +`"GoogleMap" with containerTagName is deprecated now and will be removed in next major release (5.0.0). +Use "GoogleMapLoader" instead. See https://github.com/tomchentw/react-google-maps/pull/157 for more details.` + ); } render () { - const {containerTagName, containerProps, children, ...mapProps} = this.props; - const child = this.state.map ? ( - // Notice: implementation details - // - // In this state, the DOM of google.maps.Map is already initialized in - // my innerHTML. Adding extra React components will not clean it - // in current version*. It will use prepend to add DOM of - // GoogleMapHolder and become a sibling of the DOM of google.maps.Map - // Not sure this is subject to change - // - // *current version: 0.13.3, 0.14.2 - // - - {children} - - ) : undefined; - - return React.createElement(containerTagName, containerProps, child); + const {containerTagName, containerProps = {}, children, ...mapProps} = this.props; + const isUsingNewBehavior = USE_NEW_BEHAVIOR_TAG_NAME === containerTagName; + + if (isUsingNewBehavior) { + return ( + + {children} + + ); + } else {//------------ Deprecated ------------ + const realContainerTagName = null == containerTagName ? `div` : containerTagName; + + return ( + + {children} + + } + /> + ); + } } } diff --git a/src/GoogleMapLoader.js b/src/GoogleMapLoader.js new file mode 100644 index 00000000..ad1cca71 --- /dev/null +++ b/src/GoogleMapLoader.js @@ -0,0 +1,69 @@ +import { + default as React, + PropTypes, + Component, +} from "react"; + +import { + default as propTypesElementOfType, +} from "react-prop-types-element-of-type"; + +import { + default as GoogleMapHolder, +} from "./creators/GoogleMapHolder"; + +const USE_NEW_BEHAVIOR_TAG_NAME = `__new_behavior__`;/* CIRCULAR_DEPENDENCY */ + +export default class GoogleMapLoader extends Component { + static propTypes = { + containerElement: PropTypes.node.isRequired, + googleMapElement: PropTypes.element.isRequired,/* CIRCULAR_DEPENDENCY. Uncomment when 5.0.0 comes: propTypesElementOfType(GoogleMap).isRequired, */ + }; + + static defaultProps = { + containerElement: (
), + }; + + state = { + map: null, + }; + + mountGoogleMap (domEl) { + if (this.state.map) { + return; + } + const {children, ...mapProps} = this.props.googleMapElement.props; + // + // Create google.maps.Map instance so that dom is initialized before + // React's children creators. + // + const map = GoogleMapHolder._createMap(domEl, mapProps); + this.setState({ map }); + } + + renderChild () { + if (this.state.map) { + // Notice: implementation details + // + // In this state, the DOM of google.maps.Map is already initialized in + // my innerHTML. Adding extra React components will not clean it + // in current version*. It will use prepend to add DOM of + // GoogleMapHolder and become a sibling of the DOM of google.maps.Map + // Not sure this is subject to change + // + // *current version: 0.13.3, 0.14.2 + // + return React.cloneElement(this.props.googleMapElement, { + map: this.state.map, + //------------ Deprecated ------------ + containerTagName: USE_NEW_BEHAVIOR_TAG_NAME, + }); + } + } + + render () { + return React.cloneElement(this.props.containerElement, { + ref: ::this.mountGoogleMap, + }, this.renderChild()); + } +} diff --git a/src/index.js b/src/index.js index a322692b..94155003 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ -export {default as GoogleMap} from "./GoogleMap"; +export {default as GoogleMapLoader} from "./GoogleMapLoader"; +export {default as GoogleMap} from "./GoogleMap"; export {default as Circle} from "./Circle"; export {default as DirectionsRenderer} from "./DirectionsRenderer"; export {default as DrawingManager} from "./DrawingManager";