From de7014455f9cbc80c0a2d959f4d526e8baa7eb1d Mon Sep 17 00:00:00 2001 From: JulianMayorga Date: Thu, 13 Apr 2017 12:17:50 -0300 Subject: [PATCH] Make library work with Immutable.js stores --- README.md | 10 ++++++---- lib/index.js | 12 ++++++++++-- lib/middleware.js | 6 ++++++ lib/types.js | 4 +++- lib/updater.js | 12 ++++++++++-- package.json | 3 ++- src/index.js | 14 ++++++++++++-- src/middleware.js | 8 +++++++- src/types.js | 4 +++- src/updater.js | 11 +++++++++-- 10 files changed, 68 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 21bfb927e..18911642e 100644 --- a/README.md +++ b/README.md @@ -462,10 +462,12 @@ Background sync is not yet supported. Coming soon. #### Use an [Immutable](https://facebook.github.io/immutable-js/) store -Stores that implement the entire store as an Immutable.js structure are currently not supported. You can use Immutable in the rest of your store, but the root object and the `offline` state branch created by Redux Offline currently needs to be vanilla JavaScript objects. - -[Contributions welcome](#contributing). - +To use an immutable store, just override `config.immutable`: +```js +const config = { + immutable: true +} +``` ## Contributing diff --git a/lib/index.js b/lib/index.js index 49759870d..1f5714e9c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,6 +9,8 @@ var _redux = require('redux'); var _reduxPersist = require('redux-persist'); +var _reduxPersistImmutable = require('redux-persist-immutable'); + var _middleware = require('./middleware'); var _updater = require('./updater'); @@ -25,6 +27,7 @@ var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPlug // eslint-disable-next-line no-unused-vars var persistor = void 0; +var autoRehydrate = _reduxPersist.autoRehydrate; var createOfflineStore = exports.createOfflineStore = function createOfflineStore(reducer, preloadedState, enhancer) { var userConfig = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; @@ -36,12 +39,17 @@ var createOfflineStore = exports.createOfflineStore = function createOfflineStor // wraps userland reducer with a top-level // reducer that handles offline state updating - var offlineReducer = (0, _updater.enhanceReducer)(reducer); + var offlineReducer = (0, _updater.enhanceReducer)(reducer, config); var offlineMiddleware = (0, _redux.applyMiddleware)((0, _middleware.createOfflineMiddleware)(config)); + if (config.immutable) { + autoRehydrate = _reduxPersistImmutable.autoRehydrate; + config.persist = _reduxPersistImmutable.persistStore; + } + // create autoRehydrate enhancer if required - var offlineEnhancer = config.persist && config.rehydrate ? (0, _redux.compose)(offlineMiddleware, enhancer, (0, _reduxPersist.autoRehydrate)()) : (0, _redux.compose)(offlineMiddleware, enhancer); + var offlineEnhancer = config.persist && config.rehydrate ? (0, _redux.compose)(offlineMiddleware, enhancer, autoRehydrate()) : (0, _redux.compose)(offlineMiddleware, enhancer); // create store var store = (0, _redux.createStore)(offlineReducer, preloadedState, offlineEnhancer); diff --git a/lib/middleware.js b/lib/middleware.js index 8e9360942..9b56e2129 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -77,6 +77,12 @@ var createOfflineMiddleware = exports.createOfflineMiddleware = function createO // find any actions to send, if any var state = store.getState(); + if (config.immutable) { + if (!state.toJS) { + throw new Error('Config.immutable is set to true but your root state is not immutable'); + } + state = state.toJS(); + } var actions = take(state, config); // if the are any actions in the queue that we are not diff --git a/lib/types.js b/lib/types.js index d55b87b96..0e2b814b0 100644 --- a/lib/types.js +++ b/lib/types.js @@ -149,7 +149,8 @@ Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_Ap retryCount: require("react").PropTypes.number.isRequired, retryToken: require("react").PropTypes.number.isRequired, retryScheduled: require("react").PropTypes.bool.isRequired - }).isRequired + }).isRequired, + toJS: require("react").PropTypes.func }) }); Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_Config", { @@ -161,6 +162,7 @@ Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_Co retry: require("react").PropTypes.func.isRequired, discard: require("react").PropTypes.func.isRequired, persistOptions: require("react").PropTypes.shape({}).isRequired, + immutable: require("react").PropTypes.bool.isRequired, persistCallback: require("react").PropTypes.func.isRequired }) }); \ No newline at end of file diff --git a/lib/updater.js b/lib/updater.js index 68b4a4252..f6f3b74e1 100644 --- a/lib/updater.js +++ b/lib/updater.js @@ -15,9 +15,11 @@ function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } -var babelPluginFlowReactPropTypes_proptype_ResultAction = require('./types').babelPluginFlowReactPropTypes_proptype_ResultAction || require('react').PropTypes.any; +var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPluginFlowReactPropTypes_proptype_Config || require('react').PropTypes.any; /* global */ +var babelPluginFlowReactPropTypes_proptype_ResultAction = require('./types').babelPluginFlowReactPropTypes_proptype_ResultAction || require('react').PropTypes.any; + var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('./types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; var babelPluginFlowReactPropTypes_proptype_OfflineState = require('./types').babelPluginFlowReactPropTypes_proptype_OfflineState || require('react').PropTypes.any; @@ -90,7 +92,7 @@ var offlineUpdater = function offlineUpdater() { return state; }; -var enhanceReducer = function enhanceReducer(reducer) { +var enhanceReducer = function enhanceReducer(reducer, config) { return function (state, action) { var offlineState = void 0; var restState = void 0; @@ -98,6 +100,12 @@ var enhanceReducer = function enhanceReducer(reducer) { var offline = state.offline, rest = _objectWithoutProperties(state, ['offline']); + if (config.immutable) { + offlineState = state.get('offline'); + restState = state.delete('offline'); + + return reducer(restState, action).set('offline', offlineUpdater(offlineState, action)); + } offlineState = offline; restState = rest; } diff --git a/package.json b/package.json index 87a69ec62..7c93bbd07 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "redux-logger": "^2.8.2" }, "dependencies": { - "redux-persist": "^4.5.0" + "redux-persist": "^4.5.0", + "redux-persist-immutable": "^4.2.0" }, "peerDependencies": { "redux": ">=3" diff --git a/src/index.js b/src/index.js index d95412acc..1ee73de0f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,11 @@ /*global $Shape*/ import type { Config } from './types'; import { applyMiddleware, createStore, compose } from 'redux'; -import { autoRehydrate } from 'redux-persist'; +import { autoRehydrate as reduxAutoRehydrate } from 'redux-persist'; +import { + persistStore as immutablePersistStore, + autoRehydrate as immutableAutoRehydrateImmutable +} from 'redux-persist-immutable'; import { createOfflineMiddleware } from './middleware'; import { enhanceReducer } from './updater'; import { applyDefaults } from './config'; @@ -12,6 +16,7 @@ import { networkStatusChanged } from './actions'; // eslint-disable-next-line no-unused-vars let persistor; +let autoRehydrate = reduxAutoRehydrate; export const createOfflineStore = ( reducer: any, @@ -26,10 +31,15 @@ export const createOfflineStore = ( // wraps userland reducer with a top-level // reducer that handles offline state updating - const offlineReducer = enhanceReducer(reducer); + const offlineReducer = enhanceReducer(reducer, config); const offlineMiddleware = applyMiddleware(createOfflineMiddleware(config)); + if (config.immutable) { + autoRehydrate = immutableAutoRehydrateImmutable; + config.persist = immutablePersistStore; + } + // create autoRehydrate enhancer if required const offlineEnhancer = config.persist && config.rehydrate ? compose(offlineMiddleware, enhancer, autoRehydrate()) diff --git a/src/middleware.js b/src/middleware.js index c1e45cb67..edc2ab837 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -52,7 +52,13 @@ export const createOfflineMiddleware = (config: Config) => (store: any) => (next const result = next(action); // find any actions to send, if any - const state: AppState = store.getState(); + let state: AppState = store.getState(); + if (config.immutable) { + if (!state.toJS) { + throw new Error('Config.immutable is set to true but your root state is not immutable'); + } + state = state.toJS(); + } const actions = take(state, config); // if the are any actions in the queue that we are not diff --git a/src/types.js b/src/types.js index 6d24d495e..d332fbe42 100644 --- a/src/types.js +++ b/src/types.js @@ -43,7 +43,8 @@ export type OfflineState = { }; export type AppState = { - offline: OfflineState + offline: OfflineState, + toJS?: Function // Any better way to say that State can be Immutable? }; type NetworkCallback = (result: boolean) => void; @@ -56,5 +57,6 @@ export type Config = { retry: (action: OfflineAction, retries: number) => ?number, discard: (error: any, action: OfflineAction, retries: number) => boolean, persistOptions: {}, + immutable: boolean, persistCallback: (callback: any) => any }; diff --git a/src/updater.js b/src/updater.js index 50b88476e..3f35b5723 100644 --- a/src/updater.js +++ b/src/updater.js @@ -1,7 +1,7 @@ // @flow /* global */ -import type { OfflineState, OfflineAction, ResultAction } from './types'; +import type { OfflineState, OfflineAction, ResultAction, Config } from './types'; import { OFFLINE_STATUS_CHANGED, OFFLINE_SCHEDULE_RETRY, @@ -85,11 +85,18 @@ const offlineUpdater = function offlineUpdater( return state; }; -export const enhanceReducer = (reducer: any) => (state: any, action: any) => { +export const enhanceReducer = (reducer: any, config: Config) => (state: any, action: any) => { let offlineState; let restState; if (typeof state !== 'undefined') { const { offline, ...rest } = state; + if (config.immutable) { + offlineState = state.get('offline'); + restState = state.delete('offline'); + + return reducer(restState, action) + .set('offline', offlineUpdater(offlineState, action)); + } offlineState = offline; restState = rest; }