From feb9a77c3227d99d3ac73f3024bfc872366dd42d Mon Sep 17 00:00:00 2001 From: Neuza Nunes Date: Thu, 29 Mar 2018 09:47:44 +0200 Subject: [PATCH 1/8] input could be object or function, to be able to pass props --- src/GoogleApiComponent.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/GoogleApiComponent.js b/src/GoogleApiComponent.js index 02076520..34cf8515 100644 --- a/src/GoogleApiComponent.js +++ b/src/GoogleApiComponent.js @@ -22,13 +22,15 @@ const defaultCreateCache = (options) => { }); }; -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); + const options = typeof input === 'function' ? input(props) : input; + const createCache = options.createCache || defaultCreateCache; + this.scriptCache = createCache(options); this.scriptCache.google.onLoad(this.onLoad.bind(this)) From 044aa37c703aeddd4136968e8ab9745be2467992 Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Thu, 29 Mar 2018 23:34:29 +0200 Subject: [PATCH 2/8] Add the configuration function to the README. --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8aa5348..9ac671a6 100644 --- a/README.md +++ b/README.md @@ -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) +``` + ## Sample Usage With Lazy-loading Google API: ```javascript @@ -90,7 +101,7 @@ initalCenter: Takes an object containing latitude and longitude coordinates. Set onClick={this.onMapClicked} > ``` -center: Takes an object containing latitude and longitude coordinates. Use this if you want to re-render the map after the initial render. +center: Takes an object containing latitude and longitude coordinates. Use this if you want to re-render the map after the initial render. ```javascript ``` -It also takes event handlers described below: +It also takes event handlers described below: ### Events From 62b6592c5d1d813af9b2d818d7254882f433c132 Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Fri, 6 Apr 2018 20:50:48 +0200 Subject: [PATCH 3/8] #172: Add possibility to rebuild input props for Google API HOC --- src/GoogleApiComponent.js | 52 +++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/GoogleApiComponent.js b/src/GoogleApiComponent.js index 389a855f..53d2c0f1 100644 --- a/src/GoogleApiComponent.js +++ b/src/GoogleApiComponent.js @@ -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; @@ -30,19 +34,57 @@ export const wrapper = input => WrappedComponent => { class Wrapper extends React.Component { constructor(props, context) { super(props, context); + + // 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, + 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 + this.setState({ options: options }); + } + + initialize(options) { + // Load cache factory const createCache = options.createCache || defaultCreateCache; + // Build script this.scriptCache = createCache(options); this.scriptCache.google.onLoad(this.onLoad.bind(this)); + + // Store information about loading container this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer; - this.state = { - loaded: false, - map: null, - google: null - }; + // Remove information about previous API handlers + this.setState({ loaded: false, google: null }); } onLoad(err, tag) { From 31e64f9c60dae0bbf5f82081d222b153bcba6168 Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Fri, 6 Apr 2018 20:58:34 +0200 Subject: [PATCH 4/8] #172: Rebuild distributable package --- dist/GoogleApiComponent.js | 62 +++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/dist/GoogleApiComponent.js b/dist/GoogleApiComponent.js index 942c15a1..489ebef4 100644 --- a/dist/GoogleApiComponent.js +++ b/dist/GoogleApiComponent.js @@ -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; @@ -106,31 +114,71 @@ ); }; - 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 + this.setState({ options: options }); + } + }, { + key: 'initialize', + value: function initialize(options) { + // Load cache factory + var createCache = options.createCache || defaultCreateCache; + + // Build script + this.scriptCache = createCache(options); + this.scriptCache.google.onLoad(this.onLoad.bind(this)); + + // Store information about loading container + this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer; + + // Remove information about previous API handlers + this.setState({ loaded: false, google: null }); + } + }, { key: 'onLoad', value: function onLoad(err, tag) { this._gapi = window.google; From edbed21e4842510844131e4d56a53fd0935db39a Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Fri, 6 Apr 2018 21:21:30 +0200 Subject: [PATCH 5/8] #172: Avoid race condition - allow unregistering 'load' event --- src/GoogleApiComponent.js | 9 ++++++++- src/lib/ScriptCache.js | 14 +++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/GoogleApiComponent.js b/src/GoogleApiComponent.js index 53d2c0f1..3713cf08 100644 --- a/src/GoogleApiComponent.js +++ b/src/GoogleApiComponent.js @@ -72,12 +72,19 @@ export const wrapper = input => WrappedComponent => { } 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.scriptCache.google.onLoad(this.onLoad.bind(this)); + this.unregisterLoadHandler = + this.scriptCache.google.onLoad(this.onLoad.bind(this)); // Store information about loading container this.LoadingContainer = diff --git a/src/lib/ScriptCache.js b/src/lib/ScriptCache.js index 834ccf0e..b6beff2e 100644 --- a/src/lib/ScriptCache.js +++ b/src/lib/ScriptCache.js @@ -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; } } From 692d88a070ebd954696eb295816336387d362099 Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Fri, 6 Apr 2018 21:22:16 +0200 Subject: [PATCH 6/8] #172: Rebuild distributable package --- dist/GoogleApiComponent.js | 8 +++++++- dist/lib/ScriptCache.js | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/dist/GoogleApiComponent.js b/dist/GoogleApiComponent.js index 489ebef4..e932200b 100644 --- a/dist/GoogleApiComponent.js +++ b/dist/GoogleApiComponent.js @@ -165,12 +165,18 @@ }, { 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.scriptCache.google.onLoad(this.onLoad.bind(this)); + this.unregisterLoadHandler = this.scriptCache.google.onLoad(this.onLoad.bind(this)); // Store information about loading container this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer; diff --git a/dist/lib/ScriptCache.js b/dist/lib/ScriptCache.js index 75f710ef..9e77091c 100644 --- a/dist/lib/ScriptCache.js +++ b/dist/lib/ScriptCache.js @@ -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; }; }; From 5fedb3799f8fb5f592a7bf8dde73a6e113901c14 Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Fri, 6 Apr 2018 23:04:02 +0200 Subject: [PATCH 7/8] #172: Don't use `setState` in constructor --- src/GoogleApiComponent.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/GoogleApiComponent.js b/src/GoogleApiComponent.js index 3713cf08..844e0e11 100644 --- a/src/GoogleApiComponent.js +++ b/src/GoogleApiComponent.js @@ -67,8 +67,13 @@ export const wrapper = input => WrappedComponent => { // Initialize with new options this.initialize(options); - // Save new options in component state - this.setState({ options: options }); + // Save new options in component state, + // and remove information about previous API handlers + this.setState({ + options: options, + loaded: false, + google: null + }); } initialize(options) { @@ -89,9 +94,6 @@ export const wrapper = input => WrappedComponent => { // Store information about loading container this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer; - - // Remove information about previous API handlers - this.setState({ loaded: false, google: null }); } onLoad(err, tag) { From 14878541b61aef596a8c0cff82479de2ee99eedb Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Fri, 6 Apr 2018 23:04:25 +0200 Subject: [PATCH 8/8] #172: Rebuild distributable package --- dist/GoogleApiComponent.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dist/GoogleApiComponent.js b/dist/GoogleApiComponent.js index e932200b..49abc12a 100644 --- a/dist/GoogleApiComponent.js +++ b/dist/GoogleApiComponent.js @@ -159,8 +159,13 @@ // Initialize with new options this.initialize(options); - // Save new options in component state - this.setState({ options: options }); + // Save new options in component state, + // and remove information about previous API handlers + this.setState({ + options: options, + loaded: false, + google: null + }); } }, { key: 'initialize', @@ -180,9 +185,6 @@ // Store information about loading container this.LoadingContainer = options.LoadingContainer || DefaultLoadingContainer; - - // Remove information about previous API handlers - this.setState({ loaded: false, google: null }); } }, { key: 'onLoad',