redux-localstorage
Advanced tools
Comparing version 0.4.0 to 1.0.0-beta1
@@ -13,69 +13,82 @@ 'use strict'; | ||
var _createSlicerJs = require('./createSlicer.js'); | ||
var _redux = require('redux'); | ||
var _createSlicerJs2 = _interopRequireDefault(_createSlicerJs); | ||
var _adaptersLocalStorage = require('./adapters/localStorage'); | ||
var _utilMergeStateJs = require('./util/mergeState.js'); | ||
var _adaptersLocalStorage2 = _interopRequireDefault(_adaptersLocalStorage); | ||
var _mergeStateJs = require('./mergeState.js'); | ||
var _mergeStateJs2 = _interopRequireDefault(_mergeStateJs); | ||
var _bufferActionsJs = require('./bufferActions.js'); | ||
var _bufferActionsJs2 = _interopRequireDefault(_bufferActionsJs); | ||
var ActionTypes = { | ||
INIT: 'redux-localstorage/INIT' | ||
}; | ||
function liftReducer(reducer) { | ||
return function liftedReducer(state, action) { | ||
return action.type === ActionTypes.INIT ? (0, _mergeStateJs2['default'])(state, action.payload) : reducer(state, action); | ||
}; | ||
} | ||
function persistStateMiddleware(store, storage, key) { | ||
return function (next) { | ||
return function (action) { | ||
next(action); | ||
if (action.type === ActionTypes.INIT) return; | ||
storage.put(key, store.getState(), function (err) { | ||
if (err) console.error('Unable to persist state to localStorage:', err); | ||
}); | ||
}; | ||
}; | ||
} | ||
/** | ||
* @description | ||
* persistState is a Store Enhancer that syncs (a subset of) store state to localStorage. | ||
* persistState is a Store Enhancer that persists store changes. | ||
* | ||
* @param {String|String[]} [paths] Specify keys to sync with localStorage, if left undefined the whole store is persisted | ||
* @param {Object} [config] Optional config object | ||
* @param {String} [config.key="redux"] String used as localStorage key | ||
* @param {Function} [config.slicer] (paths) => (state) => subset. A function that returns a subset | ||
* of store state that should be persisted to localStorage | ||
* @param {Function} [config.serialize=JSON.stringify] (subset) => serializedData. Called just before persisting to | ||
* localStorage. Should transform the subset into a format that can be stored. | ||
* @param {Function} [config.deserialize=JSON.parse] (persistedData) => subset. Called directly after retrieving | ||
* persistedState from localStorage. Should transform the data into the format expected by your application | ||
* @param {Object} [storage = adapter(localStorage)] Object used to interface with any type of storage back-end. | ||
* @param {String} [key = "redux-localstorage"] String used as storage key. | ||
* | ||
* @return {Function} An enhanced store | ||
* @return {Function} An enhanced store. | ||
*/ | ||
var _utilMergeStateJs2 = _interopRequireDefault(_utilMergeStateJs); | ||
function persistState(storage, key) { | ||
key = key || 'redux-localstorage'; | ||
function persistState(paths, config) { | ||
var cfg = _extends({ | ||
key: 'redux', | ||
merge: _utilMergeStateJs2['default'], | ||
slicer: _createSlicerJs2['default'], | ||
serialize: JSON.stringify, | ||
deserialize: JSON.parse | ||
}, config); | ||
if (typeof storage === 'undefined') { | ||
storage = (0, _adaptersLocalStorage2['default'])(localStorage); | ||
} else if (typeof storage === 'string') { | ||
key = storage; | ||
storage = (0, _adaptersLocalStorage2['default'])(localStorage); | ||
} | ||
var key = cfg.key; | ||
var merge = cfg.merge; | ||
var slicer = cfg.slicer; | ||
var serialize = cfg.serialize; | ||
var deserialize = cfg.deserialize; | ||
return function (next) { | ||
return function (reducer, initialState) { | ||
var persistedState = undefined; | ||
var finalInitialState = undefined; | ||
// Check if ActionTypes.INIT is already handled, "lift" reducer if not | ||
if (typeof reducer(undefined, { type: ActionTypes.INIT }) !== 'undefined') reducer = liftReducer(reducer); | ||
try { | ||
persistedState = deserialize(localStorage.getItem(key)); | ||
finalInitialState = merge(initialState, persistedState); | ||
} catch (e) { | ||
console.warn('Failed to retrieve initialize state from localStorage:', e); | ||
} | ||
// Apply middleware | ||
var store = next(reducer, initialState); | ||
var dispatch = (0, _redux.compose)((0, _bufferActionsJs2['default'])(), persistStateMiddleware(store, storage, key), store.dispatch); | ||
var store = next(reducer, finalInitialState); | ||
var slicerFn = slicer(paths); | ||
// Retrieve and dispatch persisted store state | ||
storage.get(key, function (err, persistedState) { | ||
if (err) console.error('Failed to retrieve initialize state from localStorage:', err); | ||
dispatch({ | ||
type: ActionTypes.INIT, | ||
meta: { BUFFER_BUSTER: true }, | ||
payload: persistedState | ||
}); | ||
}); | ||
store.subscribe(function () { | ||
var state = store.getState(); | ||
var subset = slicerFn(state); | ||
try { | ||
localStorage.setItem(key, serialize(subset)); | ||
} catch (e) { | ||
console.warn('Unable to persist state to localStorage:', e); | ||
} | ||
return _extends({}, store, { | ||
dispatch: dispatch | ||
}); | ||
return store; | ||
}; | ||
@@ -82,0 +95,0 @@ }; |
{ | ||
"name": "redux-localstorage", | ||
"version": "0.4.0", | ||
"version": "1.0.0-beta1", | ||
"description": "Store enhancer that syncs (a subset of) your redux store state to localstorage.", | ||
@@ -5,0 +5,0 @@ "main": "lib/persistState.js", |
111
README.md
redux-localstorage | ||
================== | ||
Store enhancer that syncs (a subset) of your Redux store state to localstorage. | ||
Store enhancer that persists store state changes when and where you want. | ||
Redux-localstorage includes support for [immutable collections](#immutable-data). Support for React Native's AsyncStorage is coming; watch this space. | ||
## Installation | ||
@@ -16,76 +14,83 @@ ```js | ||
import {compose, createStore} from 'redux'; | ||
import persistState from 'redux-localstorage' | ||
import adapter from 'redux-localstorage/lib/adapters/localStorage'; | ||
import {filter} from 'redux-localstorage/lib/enhancers'; | ||
import persistState from 'redux-localstorage'; | ||
const storage = compose( | ||
filter('todos'), | ||
adapter(localStorage) | ||
); | ||
const createPersistentStore = compose( | ||
persistState(/*paths, config*/), | ||
persistState(storage, 'my-storage-key'), | ||
createStore | ||
) | ||
); | ||
const store = createPersistentStore(/*reducer, initialState*/) | ||
const store = createPersistentStore(/*reducer, initialState*/); | ||
``` | ||
### persistState(paths, config) | ||
#### paths | ||
## persistState(storage, key) | ||
#### storage | ||
```js | ||
type paths = Void | String | Array<String> | ||
type storage = Object | ||
``` | ||
If left `Void`, persistState will sync Redux's complete store state with localStorage. Alternatively you may specify which part(s) of your state should be persisted. | ||
An object that provides ([enhanced](#enhancers)) methods for data persistence, retrieval and removal as put, get & del. Defaults to adapter(localStorage). | ||
**Note:** Currently no support for nested paths. Only "top-level" paths are supported, i.e. state[path]. If your needs are more complex and you require more control over | ||
which parts of your store's state should be persisted you can define your own strategy through [config.slicer](#configslicer) | ||
#### config | ||
##### config.key | ||
#### key | ||
```js | ||
type config.key = String | ||
type key = String | ||
``` | ||
The localStorage key used to store state. The default value is `redux`. | ||
The key used to store (and retrieve) persisted state. Defaults to 'redux-localstorage'. | ||
##### config.slicer | ||
## Storage | ||
Redux-localstorage can be made to work with any storage implementation - *it doesn't even have to be local!* All that is required is that the storage that is passed in exposes the following methods. | ||
```js | ||
type config.slicer = (paths: Any) => (state: Collection) => subset: Collection | ||
storage = { | ||
put: function(key, value, callback) {}, | ||
get: function(key, callback) {}, | ||
del: function(key, callback) {} | ||
}; | ||
``` | ||
Config.slicer allows you to define your own function which will be used to determine which parts should be synced with localStorage. It should look something like this: | ||
```js | ||
function myCustomSlicer (paths) { | ||
return (state) => { | ||
let subset = {} | ||
/*Custom logic goes here*/ | ||
return subset | ||
} | ||
} | ||
``` | ||
It is called with the paths argument supplied to persistState. It should return a function that will be called with the store's state, which should return a subset that matches the original shape/structure of the store - it's this subset that'll be persisted. | ||
A number of [adapters](#adapters) are provided to wrap existing storage API's so that they conform to these requirements. But like I said, you can create your own storage object and point these methods to any endpoint you like! | ||
If, for example, you want to dynamically persist parts of your store state based on a user's preference, defining your own `slicer` allows you to do that. Simply add something along the following lines to your customSlicer function: | ||
### adapters | ||
Redux-localstorage currently provides adapters for localStorage, sessionStorage and AsyncStorage. These adapters are very thin wrappers that transform these storage API's so that they meet the necessary requirements. | ||
```js | ||
paths.forEach((path) => { | ||
if (state[path].persistToLocalStorage) | ||
subset[path] = state[path] | ||
} | ||
``` | ||
import {compose, createStore} from 'redux'; | ||
import {AsyncStorage} from 'react-native'; | ||
## Immutable Data | ||
If you're using immutable collections or some other custom collection, redux-localstorage exposes a number of functions that can be overridden by providing the following config options. These allow you to specify your own tranformations based on your needs. If your using ordinary javascript Objects, Arrays or primitives, you shouldn't have to concern yourself with these options. | ||
import adapter from 'redux-localstorage/lib/adapters/AsyncStorage'; | ||
import persistState from 'redux-localstorage'; | ||
##### config.serialize | ||
```js | ||
type config.serialize = (subset: Collection) => serializedData: String | ||
const createPersistentStore = compose( | ||
persistState(adapter(AsyncStorage), 'my-storage-key'), | ||
createStore | ||
); | ||
``` | ||
The default serialization strategy is JSON.stringify. Specifying a serialize function as part of your config will override this. | ||
This function receives a single argument (the subset of your store's state about to be persisted) and should return a serialized (i.e. stringified) representation thereof. | ||
##### config.deserialize | ||
### enhancers | ||
```js | ||
type config.deserialize = (serializedData: String) => subset: Collection | ||
type enhancer = (Storage) => Storage | ||
``` | ||
The default deserialization strategy is JSON.parse. Specifying a deserialize function as part of your config will override this. | ||
This function receives a single argument (a serialized representation of your persisted state) and should return the data in a format that's expected by your application. | ||
Through functional composition it's really easy to enhance a storage object. This provides a lot of flexibility, allowing for fun stuff like: | ||
```js | ||
const storage = compose( | ||
debounce(1000), | ||
filter('todos'), | ||
serialization, | ||
errorHandling, | ||
adapter(localStorage) | ||
); | ||
##### config.merge | ||
```js | ||
type config.merge = (initialState: Collection, persistedState: Collection) => finalInitialState: Collection | ||
const createPersistentStore = compose( | ||
persistState(storage, 'my-storage-key'), | ||
createStore | ||
); | ||
``` | ||
During initialization any persisted state is merged with the initialState passed in as an argument to `createStore`. | ||
The default strategy `extends` the initialState with the persistedState. Override this function if that doesn't work for you. **Note:** this is only required if you want to merge values within an immutable collection. If your values are immutable, but the object that holds them is not, the default strategy should work just fine. | ||
Check out the [available enhancers](/tree/master/src/enhancers) and [recipes](/tree/master/recipes) to get going and create your own enhancers! | ||
## License | ||
MIT |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
12910
16
234
95
1