diff --git a/README.md b/README.md index 2ab2e9f02..005d11e3b 100644 --- a/README.md +++ b/README.md @@ -483,6 +483,21 @@ If you use Immutable in the rest of your store, but the root object, you should [Contributions welcome](#contributing). +#### Choose where the offline middleware is added + +By default, the offline middleware is inserted right before the offline store enhancer as part of its own middleware chain. If you want more control over where the middleware is inserted, consider using the alternative api, `createOffline()`. + +```js +import { createOffline } from "@redux-offline/redux-offline"; +const { middleware, enhanceReducer, enhanceStore } = createOffline(config); +const store = createStore( + enhanceReducer(rootReducer), + initialStore, + compose(applyMiddleware(middleware), enhanceStore) +); +``` + + ## Contributing Improvements and additions welcome. For large changes, please submit a discussion issue before jumping to coding; we'd hate you to waste the effort. diff --git a/examples/basic/client/package.json b/examples/basic/client/package.json index 15de0ab82..7a8610048 100644 --- a/examples/basic/client/package.json +++ b/examples/basic/client/package.json @@ -12,6 +12,7 @@ }, "scripts": { "start": "react-scripts start", + "alternative": "REACT_APP_OFFLINE_API=alternative react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" diff --git a/examples/basic/client/src/store.js b/examples/basic/client/src/store.js index fbc8b237d..9c1fbc7fb 100644 --- a/examples/basic/client/src/store.js +++ b/examples/basic/client/src/store.js @@ -1,5 +1,5 @@ import { applyMiddleware, compose, createStore } from 'redux'; -import { offline } from '@redux-offline/redux-offline'; +import { offline, createOffline } from '@redux-offline/redux-offline'; import defaultConfig from '@redux-offline/redux-offline/lib/defaults'; const initialState = { @@ -28,7 +28,7 @@ const config = { } }; -function middleware(store) { +function tickMiddleware(store) { return next => action => { if (action.type === 'Offline/SCHEDULE_RETRY') { const intervalId = setInterval(() => { @@ -40,9 +40,20 @@ function middleware(store) { }; } const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; -const store = createStore( - reducer, - composeEnhancers(offline(config), applyMiddleware(middleware)) -); + +let store; +if (process.env.REACT_APP_OFFLINE_API === 'alternative') { + const { middleware, enhanceReducer, enhanceStore } = createOffline(config); + store = createStore( + enhanceReducer(reducer), + undefined, + composeEnhancers(applyMiddleware(middleware, tickMiddleware), enhanceStore) + ); +} else { + store = createStore( + reducer, + composeEnhancers(offline(config), applyMiddleware(tickMiddleware)) + ); +} export default store; diff --git a/src/__tests__/index.js b/src/__tests__/index.js index 1157922e3..f96b97dad 100644 --- a/src/__tests__/index.js +++ b/src/__tests__/index.js @@ -1,8 +1,8 @@ -import { compose, createStore } from "redux"; +import { applyMiddleware, compose, createStore } from "redux"; import { KEY_PREFIX } from "redux-persist/lib/constants" import { AsyncNodeStorage } from "redux-persist-node-storage"; import instrument from "redux-devtools-instrument"; -import { offline } from "../index"; +import { createOffline, offline } from "../index"; import { applyDefaults } from "../config"; const storage = new AsyncNodeStorage("/tmp/storageDir"); @@ -35,7 +35,7 @@ function defaultReducer(state = { return state; } -test("creates storeEnhancer", () => { +test("offline() creates storeEnhancer", () => { const storeEnhancer = offline(defaultConfig); const store = storeEnhancer(createStore)(defaultReducer); @@ -43,6 +43,18 @@ test("creates storeEnhancer", () => { expect(store.getState).toEqual(expect.any(Function)); }); +test("createOffline() creates storeEnhancer", () => { + const { middleware, enhanceReducer, enhanceStore } = + createOffline(defaultConfig); + const reducer = enhanceReducer(defaultReducer); + const store = createStore(reducer, compose( + applyMiddleware(middleware), + enhanceStore + )); + expect(store.dispatch).toEqual(expect.any(Function)); + expect(store.getState).toEqual(expect.any(Function)); +}); + // see https://github.com/redux-offline/redux-offline/issues/31 test("supports HMR by overriding `replaceReducer()`", () => { const store = offline(defaultConfig)(createStore)(defaultReducer); diff --git a/src/index.js b/src/index.js index 931911ec7..25ef40a50 100644 --- a/src/index.js +++ b/src/index.js @@ -79,3 +79,56 @@ export const offline = (userConfig: $Shape = {}) => ( return store; }; + +export const createOffline = (userConfig: $Shape = {}) => { + const config = applyDefaults(userConfig); + + warnIfNotReduxAction(config, 'defaultCommit'); + warnIfNotReduxAction(config, 'defaultRollback'); + + const enhanceStore = (next: any) => ( + reducer: any, + preloadedState: any, + enhancer: any + ) => { + // create autoRehydrate enhancer if required + const createStore = + config.persist && config.rehydrate && config.persistAutoRehydrate + ? config.persistAutoRehydrate()(next) + : next; + + // create store + const store = createStore(reducer, preloadedState, enhancer); + + const baseReplaceReducer = store.replaceReducer.bind(store); + store.replaceReducer = function replaceReducer(nextReducer) { + return baseReplaceReducer(enhanceReducer(nextReducer, config)); + }; + + // launch store persistor + if (config.persist) { + persistor = config.persist( + store, + config.persistOptions, + config.persistCallback + ); + } + + // launch network detector + if (config.detectNetwork) { + config.detectNetwork(online => { + store.dispatch(networkStatusChanged(online)); + }); + } + + return store; + }; + + return { + middleware: createOfflineMiddleware(config), + enhanceReducer(reducer) { + return enhanceReducer(reducer, config); + }, + enhanceStore + }; +};