Skip to content
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

Immutable js root state #42

Merged
merged 2 commits into from
Oct 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,16 @@ Redux Offline supports the following configuration properties:
```js
export type Config = {
detectNetwork: (callback: NetworkCallback) => void,
persist: (store: any) => any,
effect: (effect: any, action: OfflineAction) => Promise<*>,
retry: (action: OfflineAction, retries: number) => ?number,
discard: (error: any, action: OfflineAction, retries: number) => boolean,
persistOptions: {},
defaultCommit: { type: string },
defaultRollback: { type: string }
defaultRollback: { type: string },
persist: (store: any) => any,
persistOptions: {},
persistCallback: (callback: any) => any,
persistAutoRehydrate: (config: ?{}) => (next: any) => any,
offlineStateLens: (state: any) => { get: OfflineState, set: (offlineState: ?OfflineState) => any }
};
```

Expand Down Expand Up @@ -402,6 +405,15 @@ const config = {
};
```

You can pass your persistAutoRehydrate method. For example in this way you can add a logger to the persistor.
```js
import { autoRehydrate } from 'redux-persist';

const config = {
persistAutoRehydrate: () => autoRehydrate({log: true})
};
```

If you want to replace redux-persist entirely **(not recommended)**, you can override `config.persist`. The function receives the store instance as a first parameter, and is responsible for setting any subscribers to listen for store changes to persist it.
```js
const config = {
Expand Down Expand Up @@ -465,11 +477,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.
The `offline` state branch created by Redux Offline needs to be a vanilla JavaScript object.
If your entire store is immutable you should check out [`redux-offline-immutable-config`](https://github.com/anyjunk/redux-offline-immutable-config) which provides drop-in configurations using immutable counterparts and code examples.
If you use Immutable in the rest of your store, but the root object, you should not need extra configurations.

[Contributions welcome](#contributing).


## 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.
Expand Down
23 changes: 22 additions & 1 deletion src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ import { applyDefaults } from "../config";

beforeEach(() => storage.removeItem(storageKey, noop) );

const defaultOfflineState = {
busy: false,
lastTransaction: 0,
online: true,
outbox: [],
receipts: [],
retryToken: 0,
retryCount: 0,
retryScheduled: false,
netInfo: {
isConnectionExpensive: null,
reach: 'NONE'
}
};

const state = {
offline: defaultOfflineState
};

test("creates storeEnhancer", () => {
const reducer = noop;
const storeEnhancer = offline(defaultConfig);
Expand Down Expand Up @@ -55,4 +74,6 @@ test("works with devtools store enhancer", () => {
const storage = new AsyncNodeStorage("/tmp/storageDir");
const defaultConfig = applyDefaults({ persistOptions: { storage } });
const storageKey = `${KEY_PREFIX}offline`;
function noop() {}
function noop() {
return state;
}
5 changes: 4 additions & 1 deletion src/__tests__/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { completeRetry, scheduleRetry } from '../actions';
import { OFFLINE_SEND } from '../constants';
import send from '../send';

import offlineStateLens from '../defaults/offlineStateLens'

const offlineAction = {
type: 'OFFLINE_ACTION_REQUEST',
meta: {
Expand Down Expand Up @@ -41,7 +43,8 @@ function setup(offlineState = {}) {
batch: jest.fn(outbox => outbox.slice(0, 1)),
effect: jest.fn(),
retry: jest.fn(),
discard: jest.fn()
discard: jest.fn(),
offlineStateLens,
},
store: {
getState: jest.fn(() => state),
Expand Down
8 changes: 6 additions & 2 deletions src/defaults/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import retry from './retry';
import discard from './discard';
import defaultCommit from './defaultCommit';
import defaultRollback from './defaultRollback';
import persistAutoRehydrate from './persistAutoRehydrate';
import offlineStateLens from './offlineStateLens';

export default {
rehydrate: true,
rehydrate: true, // backward compatibility, TODO remove in the next breaking change version
persist,
detectNetwork,
effect,
retry,
discard,
defaultCommit,
defaultRollback
defaultRollback,
persistAutoRehydrate,
offlineStateLens
};
11 changes: 11 additions & 0 deletions src/defaults/offlineStateLens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @flow
export default (state: any) => {
const { offline, ...rest } = state;
return {
get: offline,
set: (offlineState: any) =>
typeof offlineState === 'undefined'
? rest
: { offline: offlineState, ...rest }
};
};
4 changes: 4 additions & 0 deletions src/defaults/persistAutoRehydrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow
import { autoRehydrate } from 'redux-persist';

export default autoRehydrate;
7 changes: 3 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @flow
/* global $Shape */
import { applyMiddleware, compose } from 'redux';
import { autoRehydrate } from 'redux-persist';
import type { Config } from './types';
import { createOfflineMiddleware } from './middleware';
import { enhanceReducer } from './updater';
Expand Down Expand Up @@ -40,14 +39,14 @@ export const offline = (userConfig: $Shape<Config> = {}) => (

// 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));

// create autoRehydrate enhancer if required
const offlineEnhancer =
config.persist && config.rehydrate
? compose(offlineMiddleware, autoRehydrate())
config.persist && config.rehydrate && config.persistAutoRehydrate
? compose(offlineMiddleware, config.persistAutoRehydrate())
: offlineMiddleware;

// create store
Expand Down
16 changes: 9 additions & 7 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ export const createOfflineMiddleware = (config: Config) => (store: any) => (

// find any actions to send, if any
const state: AppState = store.getState();
const offlineAction = state.offline.outbox[0];
const offline = config.offlineStateLens(state).get;
const offlineAction = offline.outbox[0];

// if the are any actions in the queue that we are not
// yet processing, send those actions
if (
offlineAction &&
!state.offline.busy &&
!state.offline.retryScheduled &&
state.offline.online
!offline.busy &&
!offline.retryScheduled &&
offline.online
) {
send(offlineAction, store.dispatch, config, state.offline.retryCount);
send(offlineAction, store.dispatch, config, offline.retryCount);
}

if (action.type === OFFLINE_SCHEDULE_RETRY) {
Expand All @@ -35,8 +36,9 @@ export const createOfflineMiddleware = (config: Config) => (store: any) => (
});
}

if (action.type === OFFLINE_SEND && offlineAction && !state.offline.busy) {
send(offlineAction, store.dispatch, config, state.offline.retryCount);
if (action.type === OFFLINE_SEND && offlineAction && !offline.busy) {
send(offlineAction, store.dispatch, config, offline.retryCount);
}

return result;
};
8 changes: 6 additions & 2 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ type NetworkCallback = (result: boolean) => void;

export type Config = {
detectNetwork: (callback: NetworkCallback) => void,
persist: (store: any) => any,
persist: (store: any, options: {}, callback: () => void) => any,
effect: (effect: any, action: OfflineAction) => Promise<*>,
retry: (action: OfflineAction, retries: number) => ?number,
discard: (error: any, action: OfflineAction, retries: number) => boolean,
persistOptions: {},
persistCallback: (callback: any) => any,
defaultCommit: { type: string },
defaultRollback: { type: string }
defaultRollback: { type: string },
persistAutoRehydrate: (config: ?{}) => (next: any) => any,
offlineStateLens: (
state: any
) => { get: OfflineState, set: (offlineState: ?OfflineState) => any }
};
28 changes: 17 additions & 11 deletions src/updater.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// @flow
/* global */

import type { OfflineState, OfflineAction, ResultAction } from './types';
/* global $Shape */

import type {
OfflineState,
OfflineAction,
ResultAction,
Config
} from './types';
import {
OFFLINE_STATUS_CHANGED,
OFFLINE_SCHEDULE_RETRY,
Expand Down Expand Up @@ -118,17 +123,18 @@ const offlineUpdater = function offlineUpdater(
return state;
};

export const enhanceReducer = (reducer: any) => (state: any, action: any) => {
export const enhanceReducer = (reducer: any, config: $Shape<Config>) => (
state: any,
action: any
) => {
let offlineState;
let restState;
if (typeof state !== 'undefined') {
const { offline, ...rest } = state;
offlineState = offline;
restState = rest;
offlineState = config.offlineStateLens(state).get;
restState = config.offlineStateLens(state).set();
}

return {
...reducer(restState, action),
offline: offlineUpdater(offlineState, action)
};
return config
.offlineStateLens(reducer(restState, action))
.set(offlineUpdater(offlineState, action));
};
Loading