From af644ef1fb13eb0d83a853a2ee321515e8c91408 Mon Sep 17 00:00:00 2001
From: sergesemashko <semashkosergey@gmail.com>
Date: Sat, 30 Jan 2016 14:48:46 -0500
Subject: [PATCH] Proof of concept of deffered props

---
 modules/ReduxAsyncConnect.js | 32 +++++++++++++++++++++++++++++---
 modules/asyncConnect.js      | 36 +++++++++++++++++++++++++++++++++---
 2 files changed, 62 insertions(+), 6 deletions(-)

diff --git a/modules/ReduxAsyncConnect.js b/modules/ReduxAsyncConnect.js
index d1b684e1..1296bba0 100644
--- a/modules/ReduxAsyncConnect.js
+++ b/modules/ReduxAsyncConnect.js
@@ -35,11 +35,19 @@ function filterAndFlattenComponents(components) {
   return flattened;
 }
 
-function asyncConnectPromises(components, params, store, helpers) {
-  return components.map(Component => Component.reduxAsyncConnect(params, store, helpers))
+
+function asyncConnectDeferredPromises(components, params, store, helpers) {
+  return components.map(Component => Component.reduxAsyncConnectDeferred(params, store, helpers))
     .filter(result => result && result.then instanceof Function);
 }
 
+function asyncConnectPromises(components, params, store, helpers) {
+  return [].concat(components.map(Component => Component.reduxAsyncConnect(params, store, helpers))
+    .filter(result => result && result.then instanceof Function),
+    asyncConnectDeferredPromises(components, params, store, helpers)
+  );
+}
+
 export function loadOnServer({ components, params }, store, helpers) {
   return Promise.all(asyncConnectPromises(filterAndFlattenComponents(components), params, store, helpers))
     .catch(error => console.error('reduxAsyncConnect server promise error: ' + error)).then(() => {
@@ -84,7 +92,10 @@ class ReduxAsyncConnect extends React.Component {
   componentDidMount() {
     const dataLoaded = this.isLoaded();
 
-    if (!dataLoaded) { // we dont need it if we already made it on server-side
+    if (dataLoaded) {
+      // load deferred data if we already made initial load on server-side
+      this.loadDeferredData(this.props);
+    } else {
       this.loadAsyncData(this.props);
     }
   }
@@ -97,6 +108,21 @@ class ReduxAsyncConnect extends React.Component {
     return this.state.propsToShow !== nextState.propsToShow;
   }
 
+  loadDeferredData(props) {
+    const { components, params, helpers } = props;
+    const store = this.context.store;
+    const promises = asyncConnectDeferredPromises(filterAndFlattenComponents(components), params, store, helpers);
+
+    if (promises.length) {
+      Promise.all(promises).catch(error => console.error('reduxAsyncConnect server promise error: ' + error))
+      .then(() => {
+        this.setState({propsToShow: props});
+      });
+    } else {
+      this.setState({propsToShow: props});
+    }
+  }
+
   loadAsyncData(props) {
     const { components, params, helpers } = props;
     const store = this.context.store;
diff --git a/modules/asyncConnect.js b/modules/asyncConnect.js
index a6b86b96..31c9e75b 100644
--- a/modules/asyncConnect.js
+++ b/modules/asyncConnect.js
@@ -1,5 +1,9 @@
 import { connect } from 'react-redux';
 
+// use Function constructor to obtain a proper global object through `this`
+const isNode = (new Function("try {return this===global;}catch(e){return false;}"))();
+
+export const INIT_DEFERRED = 'reduxAsyncConnect/INIT_DEFERRED';
 export const LOAD = 'reduxAsyncConnect/LOAD';
 export const LOAD_SUCCESS = 'reduxAsyncConnect/LOAD_SUCCESS';
 export const LOAD_FAIL = 'reduxAsyncConnect/LOAD_FAIL';
@@ -7,6 +11,11 @@ export const CLEAR = 'reduxAsyncConnect/CLEAR';
 export const BEGIN_GLOBAL_LOAD = 'reduxAsyncConnect/BEGIN_GLOBAL_LOAD';
 export const END_GLOBAL_LOAD = 'reduxAsyncConnect/END_GLOBAL_LOAD';
 
+const INITIAL_DEFERRED_STATE = {
+  loading: false,
+  loaded: false
+};
+
 export function reducer(state = {loaded: false}, action = {}) {
   const stateSlice = state[action.key];
 
@@ -59,6 +68,11 @@ export function reducer(state = {loaded: false}, action = {}) {
           loading: false
         }
       };
+    case INIT_DEFERRED:
+      return {
+        ...state,
+        [action.key]: INITIAL_DEFERRED_STATE
+    };
     default:
       return state;
   }
@@ -102,6 +116,13 @@ function loadFail(key, error) {
   };
 }
 
+function initDeferred(key) {
+  return {
+    type: INIT_DEFERRED,
+    key
+  };
+}
+
 function componentLoadCb(mapStateToProps, params, store, helpers) {
   const dispatch = store.dispatch;
 
@@ -126,12 +147,21 @@ function componentLoadCb(mapStateToProps, params, store, helpers) {
   return promises.length === 0 ? null : Promise.all(promises);
 }
 
-export function asyncConnect(mapStateToProps) {
+export function asyncConnect(mapStateToProps = {}, deferredProps = {}) {
   return Component => {
-    Component.reduxAsyncConnect = (params, store, helpers) => componentLoadCb(mapStateToProps, params, store, helpers);
+
+    Component.reduxAsyncConnect = (params, store, helpers) => componentLoadCb(mapStateToProps, params, store, helpers)
+    Component.reduxAsyncConnectDeferred = (params, store, helpers) => {
+      const dispatch = store.dispatch;
+      return isNode ?
+        // Skip loading deferred props on server-side
+        Object.keys(deferredProps).map(key => dispatch(initDeferred(key))) :
+        // Load deferred props if not server
+        componentLoadCb(deferredProps, params, store, helpers);
+    }
 
     const finalMapStateToProps = state => {
-      return Object.keys(mapStateToProps).reduce((result, key) => ({...result, [key]: state.reduxAsyncConnect[key]}), {});
+      return Object.keys({...mapStateToProps, ...deferredProps}).reduce((result, key) => ({...result, [key]: state.reduxAsyncConnect[key]}), {});
     };
 
     return connect(finalMapStateToProps)(Component);