Skip to content

Commit 96ee89c

Browse files
committed
init
0 parents  commit 96ee89c

File tree

5 files changed

+203
-0
lines changed

5 files changed

+203
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
lib

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 darthmaim
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# redux-sync-reducer
2+
3+
High order reducer to sync states between tabs.
4+
5+
## Installation
6+
7+
```
8+
npm install --save redux-sync-reducer
9+
```
10+
11+
## Usage
12+
13+
Wrap the reducers you want synced between tabs with `syncedReducer`.
14+
15+
```js
16+
import { syncedReducer } from 'redux-sync-reducer';
17+
18+
syncedReduer(reducer);
19+
```
20+
21+
You also need to add the `syncMiddleware` when creating your store.
22+
This middleware will dispatch actions when the synced state gets changed in another tab.
23+
24+
```js
25+
import { syncMiddleware } from 'redux-sync-reducer';
26+
27+
const store = createStore(reducers, applyMiddleware(syncMiddleware));
28+
```
29+
30+
## API
31+
32+
```js
33+
import { syncedReducer } from 'redux-sync-reducer';
34+
syncedReducer(reducer)
35+
syncedReducer(reducer, config)
36+
```
37+
38+
## Config
39+
40+
You can pass a config object to `syncedReducer`.
41+
42+
option | default | description
43+
--------------- | ----------- | ------------
44+
**name** | `reducer.toString()` | Pass a custom name for the reducer.<br>See [why you might need this](#When-using-multiple-different-syncedReducers-all-receive-the-same-state).
45+
**skipReducer** | `false` | When the internal action is dispatched, it will call your own reducer with the new value. You can skip this by setting `skipReducer` to `true`.
46+
47+
## Common issues
48+
49+
### When using multiple different `syncedReducers` all receive the same state
50+
51+
You are probably wrapping your reducers in another high order reducer (for example `handleAction` from `redux-actions`) before passing it to `syncedReducer`. `syncedReducer` can't distinguish between the different reducers and you have to set a custom
52+
name when creating it.
53+
54+
```js
55+
export const counter = syncedReducer(handleAction(INCREASE, state => state + 1, { name: 'counter' }));
56+
```
57+
58+
## License
59+
60+
**redux-sync-reducer** is licensed under the [MIT License](LICENSE).

package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "redux-sync-reducer",
3+
"version": "0.0.1",
4+
"description": "High order reducer to sync partial states between tabs",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"clean": "rimraf lib",
9+
"build": "BABEL_ENV=commonjs babel src --out-dir lib",
10+
"prepublish": "npm run clean && npm run build"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "https://github.com/darthmaim/redux-sync-reducer.git"
15+
},
16+
"keywords": [
17+
"redux",
18+
"sync"
19+
],
20+
"author": "darthmaim",
21+
"license": "MIT",
22+
"bugs": {
23+
"url": "https://github.com/darthmaim/redux-sync-reducer/issues"
24+
},
25+
"homepage": "https://github.com/darthmaim/redux-sync-reducer#readme",
26+
"devDependencies": {
27+
"babel-cli": "^6.24.0",
28+
"babel-preset-es2015": "^6.24.0",
29+
"rimraf": "^2.6.1"
30+
},
31+
"babel": {
32+
"presets": [
33+
"es2015"
34+
]
35+
}
36+
}

src/index.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Checks if localStorage is supported.
3+
* @constant
4+
* @type {Boolean}
5+
*/
6+
const isSupported = !!(window && window.localStorage);
7+
8+
/**
9+
* List of all reducer names that are synced.
10+
* @type {string[]}
11+
*/
12+
const syncedReducers = [];
13+
14+
15+
/**
16+
* Get the type of the action.
17+
* @param {string} name
18+
*/
19+
function getActionType(name) {
20+
return `@@sync-reducer/sync/${name}`;
21+
}
22+
23+
/**
24+
* Get the key used in localStorage.
25+
*/
26+
function getKeyName(name) {
27+
return `@@sync-reducer/${name}`;
28+
}
29+
30+
/**
31+
* Sync data between tabs.
32+
* @param {string} name
33+
* @param {object} data
34+
*/
35+
function sync(name, data) {
36+
if(isSupported) {
37+
window.localStorage.setItem(getKeyName(name), JSON.stringify(data));
38+
}
39+
40+
return data;
41+
}
42+
43+
/**
44+
* High level reducer to wrap reducers to sync the state between tabs.
45+
* @param {function} reducer
46+
* @param {object} config
47+
*/
48+
export function syncedReducer(reducer, config = {}) {
49+
const name = config.name || reducer.toString();
50+
const actionType = getActionType(name);
51+
52+
syncedReducers.push(name);
53+
54+
return (state, action = {}, ...slices) => {
55+
switch(action.type) {
56+
case actionType:
57+
return config.skipReducer ? action.payload : reducer(action.payload, action, ...slices);
58+
default:
59+
return sync(name, reducer(state, action, ...slices));
60+
}
61+
}
62+
}
63+
64+
/**
65+
* Registers storage event listener and dispatches actions when the state gets changed in different tabs.
66+
*/
67+
export const syncMiddleware = store => {
68+
isSupported && window.addEventListener('storage', e => {
69+
syncedReducers.some(name => {
70+
if(e.key === getKeyName(name)) {
71+
store.dispatch({
72+
type: getActionType(name),
73+
payload: JSON.parse(e.newValue)
74+
});
75+
76+
return true;
77+
}
78+
79+
return false;
80+
})
81+
});
82+
83+
return next => action => next(action);
84+
}

0 commit comments

Comments
 (0)