├── .gitignore ├── .npmignore ├── .babelrc ├── package.json ├── README.md └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-batch-enhancer", 3 | "version": "0.1.3", 4 | "description": "Batch subscriber notification for an array of actions (including complex actions, e.g. thunks).", 5 | "main": "index.js", 6 | "main": "lib/index.js", 7 | "scripts": { 8 | "build": "babel src --out-dir lib", 9 | "prepublish": "npm run build" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/abc123s/redux-batch-enhancer.git" 14 | }, 15 | "keywords": [ 16 | "redux", 17 | "batch", 18 | "batched", 19 | "action", 20 | "subscribe", 21 | "reactjs", 22 | "react" 23 | ], 24 | "author": "Peter Zhang ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/abc123s/redux-batch-enhancer/issues" 28 | }, 29 | "homepage": "https://github.com/abc123s/redux-batch-enhancer#readme", 30 | "devDependencies": { 31 | "babel-cli": "^6.10.1", 32 | "babel-preset-es2015": "^6.9.0", 33 | "babel-preset-stage-1": "^6.5.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-batch-enhancer 2 | Store enhancer and action creator that enables batching subscriber notifications for an array of actions, including complex actions (e.g. thunks) enabled by middleware. Inspired by [redux-batched-actions](https://github.com/tshelburne/redux-batched-actions) and [redux-batched-subscribe](https://github.com/tappleby/redux-batched-subscribe). 3 | 4 | # Installation 5 | ``` 6 | npm install --save redux-batch-enhancer 7 | ``` 8 | Then to enable, apply the provided store enhancer (```batchStoreEnhancer```) and middleware (```batchMiddleware```): 9 | ```javascript 10 | import { createStore } from 'redux' 11 | import { batchStoreEnhancer, batchMiddleware } from 'redux-batch-enhancer' 12 | 13 | const store = createStore( 14 | reducer, 15 | compose( 16 | applyMiddleware(batchMiddleware, thunk, logger, perflogger), 17 | batchStoreEnhancer, 18 | ) 19 | ); 20 | ``` 21 | Note: ```batchStoreEnhancer``` overwrites dispatch and subscribe on the original redux store, and thus must be applied before any middleware or store enhancers that depend on these two methods. 22 | 23 | # Example Usage 24 | ```javascript 25 | import { batchActions } from 'redux-batch-enhancer' 26 | 27 | dispatch(batchActions([{ type: 'vanillaAction' }, createThunkAction()])); 28 | ``` 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export const BATCH = 'ENHANCED_BATCHING.BATCH'; 2 | export const PUSH = 'ENHANCED_BATCHING.PUSH'; 3 | export const POP = 'ENHANCED_BATCHING.POP'; 4 | 5 | export function batchActions(actions) { 6 | return { type: BATCH, payload: actions }; 7 | } 8 | 9 | export function batchMiddleware({ dispatch }) { 10 | return (next) => 11 | (action) => { 12 | switch (action.type) { 13 | case BATCH: { 14 | dispatch({ type: PUSH }); 15 | const returnArray = []; 16 | action.payload.forEach((batchedAction) => { 17 | returnArray.push(dispatch(batchedAction)); 18 | }); 19 | dispatch({ type: POP }); 20 | return returnArray; 21 | } 22 | default: { 23 | return next(action); 24 | } 25 | } 26 | }; 27 | } 28 | 29 | export function batchStoreEnhancer(next) { 30 | let currentListeners = []; 31 | let nextListeners = currentListeners; 32 | 33 | function ensureCanMutateNextListeners() { 34 | if (nextListeners === currentListeners) { 35 | nextListeners = currentListeners.slice(); 36 | } 37 | } 38 | 39 | function subscribe(listener) { 40 | if (typeof listener !== 'function') { 41 | throw new Error('Expected listener to be a function.'); 42 | } 43 | 44 | let isSubscribed = true; 45 | 46 | ensureCanMutateNextListeners(); 47 | nextListeners.push(listener); 48 | 49 | return function unsubscribe() { 50 | if (!isSubscribed) { 51 | return; 52 | } 53 | 54 | isSubscribed = false; 55 | 56 | ensureCanMutateNextListeners(); 57 | const index = nextListeners.indexOf(listener); 58 | nextListeners.splice(index, 1); 59 | }; 60 | } 61 | 62 | function notifyListeners() { 63 | const listeners = currentListeners = nextListeners; 64 | for (let i = 0; i < listeners.length; i++) { 65 | listeners[i](); 66 | } 67 | } 68 | 69 | return (...args) => { 70 | const store = next(...args); 71 | const subscribeImmediate = store.subscribe; 72 | 73 | let batchDepth = 0; 74 | function dispatch(...dispatchArgs) { 75 | dispatchArgs.forEach((arg) => { 76 | if (arg.type) { 77 | if (arg.type === PUSH) { 78 | batchDepth += 1; 79 | } else if (arg.type === POP) { 80 | batchDepth -= 1; 81 | } 82 | } 83 | }); 84 | const res = store.dispatch(...dispatchArgs); 85 | if (batchDepth === 0) { 86 | notifyListeners(); 87 | } 88 | return res; 89 | } 90 | 91 | return { 92 | ...store, 93 | dispatch, 94 | subscribe, 95 | subscribeImmediate, 96 | }; 97 | }; 98 | } 99 | --------------------------------------------------------------------------------