Skip to content

#172 improvement: Allow Google HOC to be initialized from props #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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