diff --git a/lib/index.js b/lib/index.js index 7e5c6f4e..978dc278 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,6 +13,15 @@ Object.defineProperty(exports, "withScriptjs", { }, }) +var _withLoadjs = require("./withLoadjs") + +Object.defineProperty(exports, "withLoadjs", { + enumerable: true, + get: function get() { + return _interopRequireDefault(_withLoadjs).default + }, +}) + var _withGoogleMap = require("./withGoogleMap") Object.defineProperty(exports, "withGoogleMap", { diff --git a/lib/withLoadjs.js b/lib/withLoadjs.js new file mode 100644 index 00000000..a394326a --- /dev/null +++ b/lib/withLoadjs.js @@ -0,0 +1,209 @@ +"use strict" + +Object.defineProperty(exports, "__esModule", { + value: true, +}) + +var _objectWithoutProperties2 = require("babel-runtime/helpers/objectWithoutProperties") + +var _objectWithoutProperties3 = _interopRequireDefault( + _objectWithoutProperties2 +) + +var _getPrototypeOf = require("babel-runtime/core-js/object/get-prototype-of") + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf) + +var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck") + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2) + +var _createClass2 = require("babel-runtime/helpers/createClass") + +var _createClass3 = _interopRequireDefault(_createClass2) + +var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn") + +var _possibleConstructorReturn3 = _interopRequireDefault( + _possibleConstructorReturn2 +) + +var _inherits2 = require("babel-runtime/helpers/inherits") + +var _inherits3 = _interopRequireDefault(_inherits2) + +var _bind2 = require("lodash/bind") + +var _bind3 = _interopRequireDefault(_bind2) + +exports.withLoadjs = withLoadjs + +var _invariant = require("invariant") + +var _invariant2 = _interopRequireDefault(_invariant) + +var _canUseDom = require("can-use-dom") + +var _canUseDom2 = _interopRequireDefault(_canUseDom) + +var _recompose = require("recompose") + +var _propTypes = require("prop-types") + +var _propTypes2 = _interopRequireDefault(_propTypes) + +var _react = require("react") + +var _react2 = _interopRequireDefault(_react) + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj } +} + +var LOADING_STATE_NONE = "NONE" +var LOADING_STATE_BEGIN = "BEGIN" +var LOADING_STATE_LOADED = "LOADED" +var LOADING_STATE_FAILED = "FAILED" + +function withLoadjs(BaseComponent) { + var factory = _react2.default.createFactory(BaseComponent) + + var Container = (function(_React$PureComponent) { + ;(0, _inherits3.default)(Container, _React$PureComponent) + + function Container() { + var _ref + + var _temp, _this, _ret + + ;(0, _classCallCheck3.default)(this, Container) + + for ( + var _len = arguments.length, args = Array(_len), _key = 0; + _key < _len; + _key++ + ) { + args[_key] = arguments[_key] + } + + return ( + (_ret = ((_temp = ((_this = (0, _possibleConstructorReturn3.default)( + this, + (_ref = + Container.__proto__ || + (0, _getPrototypeOf2.default)(Container)).call.apply( + _ref, + [this].concat(args) + ) + )), + _this)), + (_this.state = { + loadingState: LOADING_STATE_NONE, + }), + (_this.isUnmounted = false), + (_this.handleLoaded = (0, _bind3.default)(_this.handleLoaded, _this)), + _temp)), + (0, _possibleConstructorReturn3.default)(_this, _ret) + ) + } + + ;(0, _createClass3.default)(Container, [ + { + key: "handleLoaded", + value: function handleLoaded(error) { + if (this.isUnmounted) { + return + } + this.setState({ + loadingState: error ? LOADING_STATE_FAILED : LOADING_STATE_LOADED, + }) + }, + }, + { + key: "componentWillMount", + value: function componentWillMount() { + var _props = this.props, + loadingElement = _props.loadingElement, + googleMapURL = _props.googleMapURL + + ;(0, _invariant2.default)( + !!loadingElement && !!googleMapURL, + "Required props loadingElement or googleMapURL is missing. You need to provide both of them." + ) + }, + }, + { + key: "componentDidMount", + value: function componentDidMount() { + var _this2 = this + + var loadingState = this.state.loadingState + + if (loadingState !== LOADING_STATE_NONE || !_canUseDom2.default) { + return + } + this.setState({ + loadingState: LOADING_STATE_BEGIN, + }) + // Don't load scriptjs as a dependency since we do not want this module be used on server side. + // eslint-disable-next-line global-require + var loadjs = require("load-js") + var googleMapURL = this.props.googleMapURL + + loadjs([ + { + url: googleMapURL, + async: true, + }, + ]) + .then(function() { + return _this2.handleLoaded() + }) + .catch(this.handleLoaded) + }, + }, + { + key: "componentWillUnmount", + value: function componentWillUnmount() { + this.isUnmounted = true + }, + }, + { + key: "render", + value: function render() { + var _props2 = this.props, + loadingElement = _props2.loadingElement, + failedElement = _props2.failedElement, + googleMapURL = _props2.googleMapURL, + restProps = (0, _objectWithoutProperties3.default)(_props2, [ + "loadingElement", + "failedElement", + "googleMapURL", + ]) + var loadingState = this.state.loadingState + + if (loadingState === LOADING_STATE_LOADED) { + return factory(restProps) + } else if (loadingState === LOADING_STATE_FAILED) { + return failedElement + } else { + return loadingElement + } + }, + }, + ]) + return Container + })(_react2.default.PureComponent) + + Container.displayName = + "withLoadjs(" + (0, _recompose.getDisplayName)(BaseComponent) + ")" + Container.propTypes = { + loadingElement: _propTypes2.default.node.isRequired, + failedElement: _propTypes2.default.node.isRequired, + googleMapURL: _propTypes2.default.string.isRequired, + } + + return Container +} + +exports.default = withLoadjs diff --git a/package.json b/package.json index e3d34ff9..0c2190bc 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "can-use-dom": "^0.1.0", "google-maps-infobox": "^2.0.0", "invariant": "^2.2.1", + "load-js": "^3.0.2", "lodash": "^4.16.2", "marker-clusterer-plus": "^2.1.4", "markerwithlabel": "^2.0.1", diff --git a/src/index.js b/src/index.js index 2b40d439..0744111d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,7 @@ export { default as withScriptjs } from "./withScriptjs" +export { default as withLoadjs } from "./withLoadjs" + export { default as withGoogleMap } from "./withGoogleMap" export { default as GoogleMap } from "./components/GoogleMap" diff --git a/src/withLoadjs.jsx b/src/withLoadjs.jsx new file mode 100644 index 00000000..7a3074bb --- /dev/null +++ b/src/withLoadjs.jsx @@ -0,0 +1,99 @@ +import _ from "lodash" +import invariant from "invariant" +import canUseDOM from "can-use-dom" +import { getDisplayName } from "recompose" +import PropTypes from "prop-types" +import React from "react" + +const LOADING_STATE_NONE = `NONE` +const LOADING_STATE_BEGIN = `BEGIN` +const LOADING_STATE_LOADED = `LOADED` +const LOADING_STATE_FAILED = `FAILED` + +export function withLoadjs(BaseComponent) { + const factory = React.createFactory(BaseComponent) + + class Container extends React.PureComponent { + static displayName = `withLoadjs(${getDisplayName(BaseComponent)})` + + static propTypes = { + loadingElement: PropTypes.node.isRequired, + failedElement: PropTypes.node.isRequired, + googleMapURL: PropTypes.string.isRequired, + } + + state = { + loadingState: LOADING_STATE_NONE, + } + + isUnmounted = false + + handleLoaded = _.bind(this.handleLoaded, this) + + handleLoaded(error) { + if (this.isUnmounted) { + return + } + this.setState({ + loadingState: error ? LOADING_STATE_FAILED : LOADING_STATE_LOADED, + }) + } + + componentWillMount() { + const { loadingElement, googleMapURL } = this.props + invariant( + !!loadingElement && !!googleMapURL, + `Required props loadingElement or googleMapURL is missing. You need to provide both of them.` + ) + } + + componentDidMount() { + const { loadingState } = this.state + if (loadingState !== LOADING_STATE_NONE || !canUseDOM) { + return + } + this.setState({ + loadingState: LOADING_STATE_BEGIN, + }) + // Don't load scriptjs as a dependency since we do not want this module be used on server side. + // eslint-disable-next-line global-require + const loadjs = require(`load-js`) + const { googleMapURL } = this.props + loadjs([ + { + url: googleMapURL, + async: true, + }, + ]) + .then(() => this.handleLoaded()) + .catch(this.handleLoaded) + } + + componentWillUnmount() { + this.isUnmounted = true + } + + render() { + const { + loadingElement, + failedElement, + googleMapURL, // eslint-disable-line no-unused-vars + ...restProps + } = this.props + + const { loadingState } = this.state + + if (loadingState === LOADING_STATE_LOADED) { + return factory(restProps) + } else if (loadingState === LOADING_STATE_FAILED) { + return failedElement + } else { + return loadingElement + } + } + } + + return Container +} + +export default withLoadjs diff --git a/src/withLoadjs.md b/src/withLoadjs.md new file mode 100644 index 00000000..27e6925a --- /dev/null +++ b/src/withLoadjs.md @@ -0,0 +1,35 @@ +### Props + +* googleMapURL: String +* loadingElement: ReactElement +* failedElement: ReactElement + +### Usage + +```jsx static +import { + withLoadjs, + withGoogleMap, + GoogleMap, + Marker, +} from "react-google-maps"; + +const MapWithAMarker = withLoadjs(withGoogleMap(props => + + + +)); + +} + failedElement={
} + containerElement={
} + mapElement={
} +/> +``` diff --git a/styleguide.config.js b/styleguide.config.js index 5758647f..e75562c3 100644 --- a/styleguide.config.js +++ b/styleguide.config.js @@ -55,6 +55,10 @@ module.exports = { name: "withScriptjs", content: "src/withScriptjs.md", }, + { + name: "withLoadjs", + content: "src/withLoadjs.md", + }, ], }, { diff --git a/types/index.d.ts b/types/index.d.ts index bdf0fb1f..6e0568cf 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -3,6 +3,7 @@ declare module 'react-google-maps' { export { default as withGoogleMap, WithGoogleMapProps } from 'react-google-maps/lib/withGoogleMap' export { default as withScriptjs, WithScriptjsProps } from 'react-google-maps/lib/withScriptjs' + export { default as withLoadjs, WithLoadjsProps } from 'react-google-maps/lib/withLoadjs' export { default as Circle, CircleProps } from 'react-google-maps/lib/components/Circle' export { default as DirectionsRenderer, DirectionsRendererProps } from 'react-google-maps/lib/components/DirectionsRenderer' @@ -42,6 +43,18 @@ declare module 'react-google-maps/lib/withScriptjs' { export default function withScriptjs

(wrappedComponent: ComponentClass

): ComponentClass

} +declare module 'react-google-maps/lib/withLoadjs' { + import { ComponentClass, ReactElement } from 'react' + + export interface WithLoadjsProps { + googleMapURL: string + loadingElement: ReactElement + failedElement: ReactElement + } + + export default function withLoadjs

(wrappedComponent: ComponentClass

): ComponentClass

+} + declare module 'react-google-maps/lib/components/addons/InfoBox' { import { Component } from 'react' import { InfoBoxOptions } from 'google-maps-infobox' diff --git a/yarn.lock b/yarn.lock index 2a8bdddb..5487cce4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5345,6 +5345,10 @@ listr@^0.13.0: stream-to-observable "^0.2.0" strip-ansi "^3.0.1" +load-js@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/load-js/-/load-js-3.0.2.tgz#3eb5c0a0f1fd1fc45567030d9f2622534b75462c" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"