Skip to content
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ export default GoogleApiWrapper({
})(MapContainer)
```

Alternatively, the `GoogleApiWrapper` Higher-Order component can be configured by passing a function that will be called with whe wrapped component's `props` and should returned the configuration object.

```javascript
export default GoogleApiWrapper(
(props) => ({
apiKey: props.apiKey,
language: props.language,
}
))(MapContainer)
```

If you want to add a loading container _other than the default_ loading container, simply pass it in the HOC, like so:

```javascript
Expand Down
70 changes: 63 additions & 7 deletions dist/GoogleApiComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@
}

var defaultMapConfig = {};

var serialize = function serialize(obj) {
return JSON.stringify(obj);
};
var isSame = function isSame(obj1, obj2) {
return obj1 === obj2 || serialize(obj1) === serialize(obj2);
};

var defaultCreateCache = function defaultCreateCache(options) {
options = options || {};
var apiKey = options.apiKey;
Expand Down Expand Up @@ -106,31 +114,79 @@
);
};

var wrapper = exports.wrapper = function wrapper(options) {
var wrapper = exports.wrapper = function wrapper(input) {
return function (WrappedComponent) {
var createCache = options.createCache || defaultCreateCache;

var Wrapper = function (_React$Component) {
_inherits(Wrapper, _React$Component);

function Wrapper(props, context) {
_classCallCheck(this, Wrapper);

// Build options from input
var _this = _possibleConstructorReturn(this, (Wrapper.__proto__ || Object.getPrototypeOf(Wrapper)).call(this, props, context));

_this.scriptCache = createCache(options);
_this.scriptCache.google.onLoad(_this.onLoad.bind(_this));
_this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer;
var options = typeof input === 'function' ? input(props) : input;

// Initialize required Google scripts and other configured options
_this.initialize(options);

_this.state = {
loaded: false,
map: null,
google: null
google: null,
options: options
};
return _this;
}

_createClass(Wrapper, [{
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(props) {
// Do not update input if it's not dynamic
if (typeof input !== 'function') {
return;
}

// Get options to compare
var prevOptions = this.state.options;
var options = typeof input === 'function' ? input(props) : input;

// Ignore when options are not changed
if (isSame(options, prevOptions)) {
return;
}

// Initialize with new options
this.initialize(options);

// Save new options in component state,
// and remove information about previous API handlers
this.setState({
options: options,
loaded: false,
google: null
});
}
}, {
key: 'initialize',
value: function initialize(options) {
// Avoid race condition: remove previous 'load' listener
if (this.unregisterLoadHandler) {
this.unregisterLoadHandler();
this.unregisterLoadHandler = null;
}

// Load cache factory
var createCache = options.createCache || defaultCreateCache;

// Build script
this.scriptCache = createCache(options);
this.unregisterLoadHandler = this.scriptCache.google.onLoad(this.onLoad.bind(this));

// Store information about loading container
this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer;
}
}, {
key: 'onLoad',
value: function onLoad(err, tag) {
this._gapi = window.google;
Expand Down
14 changes: 13 additions & 1 deletion dist/lib/ScriptCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@

Cache._onLoad = function (key) {
return function (cb) {
var registered = true;

function unregister() {
registered = false;
}

var stored = scriptMap.get(key);

if (stored) {
stored.promise.then(function () {
stored.error ? cb(stored.error) : cb(null, stored);
if (registered) {
stored.error ? cb(stored.error) : cb(null, stored);
}

return stored;
});
} else {
// TODO:
}

return unregister;
};
};

Expand Down
67 changes: 59 additions & 8 deletions src/GoogleApiComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {ScriptCache} from './lib/ScriptCache';
import GoogleApi from './lib/GoogleApi';

const defaultMapConfig = {};

const serialize = obj => JSON.stringify(obj);
const isSame = (obj1, obj2) => obj1 === obj2 || serialize(obj1) === serialize(obj2);

const defaultCreateCache = options => {
options = options || {};
const apiKey = options.apiKey;
Expand All @@ -26,25 +30,72 @@ const defaultCreateCache = options => {

const DefaultLoadingContainer = props => <div>Loading...</div>;

export const wrapper = options => WrappedComponent => {
const createCache = options.createCache || defaultCreateCache;

export const wrapper = input => WrappedComponent => {
class Wrapper extends React.Component {
constructor(props, context) {
super(props, context);

this.scriptCache = createCache(options);
this.scriptCache.google.onLoad(this.onLoad.bind(this));
this.LoadingContainer =
options.LoadingContainer || DefaultLoadingContainer;
// Build options from input
const options = typeof input === 'function' ? input(props) : input;

// Initialize required Google scripts and other configured options
this.initialize(options);

this.state = {
loaded: false,
map: null,
google: null
google: null,
options: options
};
}

componentWillReceiveProps(props) {
// Do not update input if it's not dynamic
if (typeof input !== 'function') {
return;
}

// Get options to compare
const prevOptions = this.state.options;
const options = typeof input === 'function' ? input(props) : input;

// Ignore when options are not changed
if (isSame(options, prevOptions)) {
return;
}

// Initialize with new options
this.initialize(options);

// Save new options in component state,
// and remove information about previous API handlers
this.setState({
options: options,
loaded: false,
google: null
});
}

initialize(options) {
// Avoid race condition: remove previous 'load' listener
if (this.unregisterLoadHandler) {
this.unregisterLoadHandler();
this.unregisterLoadHandler = null;
}

// Load cache factory
const createCache = options.createCache || defaultCreateCache;

// Build script
this.scriptCache = createCache(options);
this.unregisterLoadHandler =
this.scriptCache.google.onLoad(this.onLoad.bind(this));

// Store information about loading container
this.LoadingContainer =
options.LoadingContainer || DefaultLoadingContainer;
}

onLoad(err, tag) {
this._gapi = window.google;

Expand Down
14 changes: 13 additions & 1 deletion src/lib/ScriptCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ export const ScriptCache = (function(global) {

Cache._onLoad = function(key) {
return (cb) => {
let registered = true;

function unregister() {
registered = false;
}

let stored = scriptMap.get(key);

if (stored) {
stored.promise.then(() => {
stored.error ? cb(stored.error) : cb(null, stored)
if (registered) {
stored.error ? cb(stored.error) : cb(null, stored)
}

return stored;
});
} else {
// TODO:
}

return unregister;
}
}

Expand Down