├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .npmignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE.md ├── README.md ├── devtools ├── devServer.js ├── index.ejs ├── webpack.config.config.js ├── webpack.config.dev.js ├── webpack.config.dist.js ├── webpack.config.dist.min.js ├── webpack.config.dlllib.dev.js └── webpack.config.lib.js ├── jsconfig.json ├── lib └── simpler-redux.js ├── package.json ├── src ├── index.js ├── proxy.js ├── react-simpler-redux.js ├── simpler-redux.js └── util.js └── test ├── App.jsx ├── Counter ├── index.js ├── model.js └── view.jsx ├── Counter1 ├── index.js ├── model.js └── view.jsx ├── Counter2 ├── index.js ├── model.js └── view.jsx ├── Counter3 ├── index.js ├── model.js └── view.jsx ├── Counter4 ├── index.js ├── model.js └── view.jsx ├── Counter5 ├── index.js ├── model.js └── view.jsx ├── Counter6 ├── index.js ├── model.js └── view.jsx ├── Counter7 ├── index.js ├── model.js └── view.jsx ├── Counter8 ├── index.js ├── model.js └── view.jsx ├── WrapCounter1 ├── index.js ├── model.js └── view.jsx ├── mountapp.jsx ├── projectsetup.js ├── reducers.js ├── reduxstore.js ├── setup.js ├── test.js ├── testApp.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ], 6 | "plugins": [ 7 | "transform-object-rest-spread", 8 | "react-hot-loader/babel", 9 | "transform-react-jsx" 10 | ], 11 | "env": { 12 | "production": { 13 | "presets": [ 14 | "react-optimize" 15 | ] 16 | }, 17 | "development": { 18 | "plugins": [ 19 | "transform-react-jsx-source" 20 | ] 21 | } 22 | }, 23 | "sourceMaps": true, 24 | "retainLines": true 25 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | devdll/** 2 | dist/** 3 | lib/** 4 | temp/** 5 | src/**/*.inject* 6 | src/**/*.scss 7 | src/**/*.sass 8 | src/**/*.less 9 | src/**/*.css 10 | src/assets/** 11 | node_modules/** 12 | .vscode/chrome/** 13 | dllbundle.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | "parser": "babel-eslint", 5 | "plugins": [ 6 | "babel", 7 | "react" 8 | ], 9 | "settings": { 10 | "import/resolver": { 11 | "webpack": { 12 | "config": path.join(__dirname, "devtools/webpack.config.dev.js") 13 | } 14 | } 15 | }, 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "jsx": true 19 | }, 20 | "sourceType": "module", 21 | "ecmaVersion": 8 22 | }, 23 | 24 | "extends": [ 25 | "standard", 26 | "standard-react", 27 | "plugin:import/warnings", 28 | "plugin:import/errors" 29 | ], 30 | "env": { 31 | "amd": true, 32 | "browser": true, 33 | "jquery": true, 34 | "node": true, 35 | "es6": true, 36 | "worker": true, 37 | "mocha": true 38 | }, 39 | "rules": { 40 | "react/prop-types": 0, 41 | "react/jsx-boolean-value": 0 42 | } 43 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/chrome/** 2 | devdll/** 3 | node_modules/** 4 | temp/** 5 | docs/** 6 | package-lock.json 7 | index.dev.ejs 8 | todo.txt 9 | test.dev/** 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | devdll/** 3 | devtools/devServer.js 4 | devtools/index.ejs 5 | node_modules/** 6 | temp/** 7 | docs/** 8 | test.dev/** 9 | .eslintignore 10 | .eslintrc.js 11 | index.dev.ejs 12 | jsconfig.json 13 | package-lock.json 14 | .gitignore 15 | .npmignore 16 | .gitattributes 17 | .babelrc 18 | docs/** 19 | todo.txt 20 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "msjsdiag.debugger-for-chrome", 4 | "dbaeumer.vscode-eslint", 5 | "xabikos.ReactSnippets", 6 | "fknop.vscode-npm", 7 | "christian-kohler.npm-intellisense" 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "chrome", 7 | "request": "launch", 8 | "webRoot": "${workspaceRoot}", 9 | "url": "http://localhost:3000/index.html", 10 | "userDataDir": "${workspaceRoot}/.vscode/chrome", 11 | "sourceMaps": true, 12 | "smartStep": true, 13 | "preLaunchTask": "Development", 14 | "internalConsoleOptions": "openOnSessionStart", 15 | "skipFiles": [ 16 | "node_modules/**" 17 | ], 18 | "sourceMapPathOverrides": { 19 | "webpack:///*": "${webRoot}/*" 20 | } 21 | }, 22 | { 23 | "name": "Run Mocha Tests", 24 | "type": "node", 25 | "request": "launch", 26 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 27 | "stopOnEntry": false, 28 | "sourceMaps": true, 29 | "smartStep": true, 30 | "args": [ 31 | "--require", 32 | "babel-register", 33 | "--require", 34 | "./test/setup.js", 35 | "./test/mountapp.jsx", 36 | "./test/projectsetup.js", 37 | "./test/test.js", 38 | "./test/testApp.js", 39 | "--no-timeouts", 40 | "--colors" 41 | ], 42 | "cwd": "${workspaceRoot}", 43 | "runtimeExecutable": null, 44 | "env": {"NODE_ENV": "testing"} 45 | }, 46 | { 47 | "name": "Debug Mocha Tests", 48 | "type": "node", 49 | "request": "launch", 50 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 51 | "stopOnEntry": false, 52 | "sourceMaps": true, 53 | "smartStep": true, 54 | "args": [ 55 | "--require", 56 | "babel-register", 57 | "--require", 58 | "./test/setup.js", 59 | "./test/mountapp.jsx", 60 | "./test/projectsetup.js", 61 | "./test/test.js", 62 | "./test/testApp.js", 63 | "--no-timeouts", 64 | "--colors" 65 | ], 66 | "cwd": "${workspaceRoot}", 67 | "runtimeExecutable": null, 68 | "env": {"NODE_ENV": "debugTesting"} 69 | } 70 | ] 71 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.formatOnType": true, 4 | "eslint.enable": true, 5 | "files.associations": { 6 | "**/src/**/*.js": "javascriptreact", 7 | "**/node_modules/**/*.js": "javascript", 8 | ".babelrc": "json", 9 | ".eslintignore": "gitignore" 10 | }, 11 | "files.watcherExclude": { 12 | ".vscode/chrome/**": true 13 | }, 14 | "html.format.extraLiners": "!", 15 | "html.format.indentInnerHtml": true, 16 | "json.schemas": [ 17 | { 18 | "fileMatch": [ 19 | "/package.json" 20 | ], 21 | "schema": { 22 | "type": "object", 23 | "properties": { 24 | "config": { 25 | "properties": { 26 | "port": { 27 | "type": "number", 28 | "description": "The port of the webpack-dev-server", 29 | "default": 3000 30 | }, 31 | "host": { 32 | "type": "string", 33 | "description": "The hostname or ip of the webpack-dev-server", 34 | "default": "localhost" 35 | }, 36 | "ghooks": { 37 | "type": "object", 38 | "description": "Git hook configurations", 39 | "properties": { 40 | "applypatch-msg": { 41 | "type": "string" 42 | }, 43 | "pre-applypatch": { 44 | "type": "string" 45 | }, 46 | "post-applypatch": { 47 | "type": "string" 48 | }, 49 | "pre-commit": { 50 | "type": "string" 51 | }, 52 | "prepare-commit-msg": { 53 | "type": "string" 54 | }, 55 | "commit-msg": { 56 | "type": "string" 57 | }, 58 | "post-commit": { 59 | "type": "string" 60 | }, 61 | "pre-rebase": { 62 | "type": "string" 63 | }, 64 | "post-checkout": { 65 | "type": "string" 66 | }, 67 | "post-merge": { 68 | "type": "string" 69 | }, 70 | "pre-push": { 71 | "type": "string" 72 | }, 73 | "pre-receive": { 74 | "type": "string" 75 | }, 76 | "update": { 77 | "type": "string" 78 | }, 79 | "post-receive": { 80 | "type": "string" 81 | }, 82 | "post-update": { 83 | "type": "string" 84 | }, 85 | "pre-auto-gc": { 86 | "type": "string" 87 | }, 88 | "post-rewrite": { 89 | "type": "string" 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | ], 99 | "search.exclude": { 100 | "dist/**": true, 101 | "lib/**": true, 102 | "temp/**": true, 103 | "node_modules": true, 104 | ".git": true, 105 | ".vscode/chrome/**": true, 106 | "devdll/**": true 107 | }, 108 | "files.exclude": { 109 | "**/.git": true, 110 | "**/.DS_Store": true, 111 | "**/node_modules": true, 112 | ".vscode/chrome/**": true 113 | }, 114 | "typescript.check.tscVersion": false 115 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | { 9 | "version": "0.1.0", 10 | "command": "npm", 11 | "isShellCommand": true, 12 | "echoCommand": false, 13 | "suppressTaskName": true, 14 | "showOutput": "always", 15 | "tasks": [ 16 | { 17 | "args": [ 18 | "run", 19 | "start", 20 | "--silent", 21 | "--react-hot-boilerplate-vscode:port=3000", 22 | "--react-hot-boilerplate-vscode:host=localhost" // use these command line args to override host and port of the dev-server. default values are defined in package.json config section. 23 | ], 24 | "problemMatcher": [ 25 | { 26 | "owner": "custom", 27 | "pattern": { 28 | "regexp": "____________" 29 | }, 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": "^webpack: Compiling\\.\\.\\.$", 33 | "endsPattern": "^webpack: (Compiled successfully|Failed to compile)\\.$" 34 | } 35 | }, 36 | { 37 | "owner": "javascript", 38 | "severity": "error", 39 | "applyTo": "closedDocuments", 40 | "fileLocation": "absolute", 41 | "pattern": [ 42 | { 43 | "regexp": "^(Module build failed:\\s.*?:\\s(.*):(.*?))[\\s+](?:\\(([\\d-]+):([\\d-]+)\\))?$", 44 | "message": 3, 45 | "file": 2, 46 | "line": 4, 47 | "column": 5 48 | } 49 | ] 50 | }, 51 | { 52 | "owner": "javascript", 53 | "severity": "error", 54 | "applyTo": "closedDocuments", 55 | "fileLocation": "relative", 56 | "pattern": [ 57 | { 58 | "regexp": "^ERROR in ./(.*)\\s?$", 59 | "file": 1 60 | }, 61 | { 62 | "regexp": "^.*?Error:\\s(.*').*$", 63 | "message": 1 64 | }, 65 | { 66 | "regexp": "^\\s+@.*?(\\d+)(?:-([\\d]+))?:(\\d+)(?:-([\\d]+))?\\s?$", 67 | "line": 1, 68 | "endLine": 2, 69 | "column": 3, 70 | "endColumn": 4 71 | } 72 | ] 73 | } 74 | ], 75 | "isBackground": true, 76 | "taskName": "Development" 77 | }, 78 | { 79 | "args": [ 80 | "run", 81 | "build", 82 | "--silent" 83 | ], 84 | "taskName": "Build Production" 85 | }, 86 | { 87 | "args": [ 88 | "run", 89 | "buildmochadebug", 90 | "--silent" 91 | ], 92 | "taskName": "BuildMochaDebug" 93 | }, 94 | { 95 | "args": [ 96 | "install", 97 | "--progress", 98 | "false", 99 | "--loglevel", 100 | "http" 101 | ], 102 | "taskName": "Install" 103 | }, 104 | { 105 | "args": [ 106 | "run", 107 | "builddll:dev", 108 | "--silent" 109 | ], 110 | "taskName": "Builddll Development" 111 | }, 112 | { 113 | "args": [ 114 | "publish" 115 | ], 116 | "taskName": "NPM Publish" 117 | }, 118 | { 119 | "args": [ 120 | "run", 121 | "lint", 122 | "--loglevel", 123 | "silent" 124 | ], 125 | "problemMatcher": [ 126 | "$eslint-stylish" 127 | ], 128 | "taskName": "Lint" 129 | } 130 | ] 131 | } 132 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Written by Andrew Banks. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | This permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Redux library to simplify and reduce redux code. 2 | 3 | ### Advantages 4 | - Simple react style setState and getState functions for redux state management. 5 | - 100% compatibility with your current react-redux code base. 6 | - Automatic generation of mapStateToProps so that you do not have to write any. 7 | - Automatic generation of mapDispatchToProps so that you do not have to write any. 8 | - Automatic generation of the reducer so that you do not have to write any. 9 | - Reduced code size and code complexity as compared to react-redux. 10 | - Each reducer contains only one compare at each redux state transition which puts a small burden on performance. 11 | - Ability to handle a react component's life cycle events in a simple way in the model code separate from the react UI. 12 | - Complete separation of the UI from the business code according to a MVC standard. 13 | - Simple implementations for shared state management and shared business code without requiring additional reducers. 14 | - Thunk middleware is not required. Asynchronous UI calls are handled in the same way as synchronous UI calls. 15 | 16 | ### Installation 17 | `npm install simpler-redux` 18 | 19 | ### Migration from react-redux to simpler-redux 20 | 1) Install the simpler-redux libraty. `npm i simpler-redux` 21 | 2) Where you call the redux `createStore`, add the following where `reduxStore` is returned from the redux function `createStore`. 22 | ```javascript 23 | // It is assumed that this file is configureStore.js 24 | import { registerSimplerRedux } from 'simpler-redux' 25 | 26 | export const simplerReduxStore = registerSimplerRedux(reduxStore) 27 | ``` 28 | 3) In your react-redux `Provider`, replace the redux store with the `simplerReduxStore` as shown below. 29 | ```javascript 30 | import React from 'react' 31 | import { Provider } from 'react-redux' 32 | import simplerReduxStore from './configureStore' 33 | import App from './App' 34 | 35 | export default () => 36 | 37 | 38 | 39 | ``` 40 | 4) Copy the simpler-redux MVC component scaffolding located [here](https://github.com/AndrewBanks10/simpler-redux-skeleton-project). It contains comments to guide your react component development. 41 | 42 | ### Basics 43 | #### _registerSimplerRedux_ 44 | **Description** 45 | `registerSimplerRedux(reduxStore[, rootReducersObject[, preloadedState[, options]]])` - Wrap this around your redux `createStore` call. It returns an enhanced reduxStore object with the redux store as its prototype. 46 | **Parameters** 47 | `reduxStore` - The redux store that is returned by createStore. 48 | `rootReducersObject` - If you want to use dynamic reducer loading, specify this object parameter. Note that this is not the combineReducers object but the actual reducers object. If this is a new project, it is recommended that you use dynamic reducer loading exclusively. Then specifify this as the empty object {} and simpler-redux will automatically build your reducer and also load it when the component is constructed. Therefore, simpler-redux automatically supports react loadables without any code rewrites. 49 | `preloadedState`- If you specify the `rootReducersObject` and have state to preload then you must specify it in this parameter. 50 | 51 | **Return Value** 52 | A simpler redux store. Add this as a store prop to the react-redux Provider element. 53 | 54 | #### _createStore_ 55 | **Description** 56 | `createStore([preloadedState[, enhancer]]])` - createStore calls redux createStore and supplies an interal reducer function, preloadedState and enhancers such as a redux logger. This solves the redux performance problem with calling every reducer on every state change. Only one reducer manages the entire redux state and does not require any case statements to merge state with the redux reducer key state(s). So, state changes only require one reducer call. However, this is incompatible with redux reducer code. Hence, only use this for simpler-redux only projects. 57 | **Parameters** 58 | `preloadedState`- redux preloadedState state. 59 | `enhancer` - redux enhancer. 60 | 61 | **Return Value** 62 | A simpler redux store. Add this as a store prop to the react-redux Provider element. 63 | 64 | #### _setRState_ 65 | **Description** 66 | `simplerReduxStore.setRState(reducerKey, objectToMerge, [type])` - Merge the objectToMerge at the redux state object `reduxState[reducerKey]`. 67 | **Parameters** 68 | - `reducerKey` - Redux reducer key name. All state transitions will occur at `reduxState[reducerKey]`. 69 | - `objectToMerge` - This object will be merged at `reduxState[reducerKey]`. 70 | - `type` - Optional string name to identify the state change. This is the same thing as the action constant. It defaults to the `reducerKey`. 71 | 72 | **Return Value** 73 | None 74 | 75 | #### _getRState_ 76 | **Description** 77 | `simplerReduxStore.getRState(reducerKey)` - Returns the state at `reduxState[reducerKey]`. 78 | **Parameters** 79 | `reducerKeyName` - Redux reducer key name. 80 | **Return Value** 81 | The state at `reduxState[reducerKey]`. 82 | 83 | #### _addListener_ 84 | **Description** 85 | `simplerReduxStore.addListener(listener)` - Adds a listener that will be called immediately after any simpler-redux state change. 86 | **Parameters** 87 | `function(reducerKey, objMerged, type){}` - The function that will be called after the state change. 88 | **Return Value** 89 | Returns a function to remove the listener from simpler-redux. 90 | 91 | #### _generalReducer_ 92 | **Description** 93 | `generalReducer(reducerKey, initialState)` - Returns a reducer function that manages the redux state transitions at `reduxState[reducerKey]`. Add this and the reducerKeyName to your root reducer object. 94 | **Parameters** 95 | - `reducerKey` - Redux reducer key name. All state transitions will occur at `reduxState[reducerKey]`. 96 | - `initialState` - This object will be merged at `reduxState[reducerKey]` for the initial state. 97 | 98 | **Return Value** 99 | A reducer function that manages the state transitions at `reducerKey`. 100 | 101 | #### _connectWithStore_ 102 | **Description** 103 | Applies the redux connect function and returns a react HOC with the simpler-redux store in the props as `props.store`. This connects your UI component with your business code `selectors` and `serviceFunctions`. 104 | ```javascript 105 | connectWithStore({ 106 | uiComponent, 107 | mapStateToProps, 108 | mapDispatchToProps, 109 | selectors, 110 | serviceFunctions, 111 | mergeProps, 112 | reduxOptions, 113 | storeIsDefinedCallback, 114 | noStoreParameterOnServiceFunctions, 115 | reducerKey, 116 | initialUIState, 117 | isDynamicReducer 118 | }) 119 | ``` 120 | **Object Parameters** 121 | - `uiComponent` - The react component that will be wrapped such that service functions and slices of state will be in the props. 122 | - `mapStateToProps` - You can write your own mapStateToProps but it is recommended that you use the selecters instead. Then a mapStateToProps will be automatically generated, based on the selectors object, and supplied to the redux connect function. 123 | - `mapDispatchToProps` - You can write your own mapDispatchToProps but it is recommended that you use the serviceFunctions instead. Then a mapDispatchToProps will be automatically generated, based on the serviceFunctions object, and supplied to the redux connect function. 124 | - `selectors` - The `selectors` is an object of functions that returns slices of the state at the reducerKey. It is used to build a mapStateToProps function for the redux connect. Below is an example. 125 | ```javascript 126 | // All keys in selectors will be in the props of the react component. 127 | // Each entry is a function that takes the entire redux state as a parameter 128 | // and then returns a slice of the state[reducerKey]. 129 | export const selectors = { 130 | counter: state => state[reducerKey].counter 131 | } 132 | ``` 133 | - `serviceFunctions` - The `serviceFunctions` is an object of functions that cause state changes at the reducerKey. It is used to build a mapDispatchToProps function for the redux connect. 134 | You can also handle business code here for react lifecycle events. So for code that is non-DOM oriented, the following keys are supported and the associated functions will be called at the particular react life cycle event. 135 | ```javascript 136 | onConstructor, 137 | onRender, 138 | componentDidMount, 139 | componentWillUnmount, 140 | componentDidCatch, 141 | componentToRender, 142 | ``` 143 | 144 | Below is an example. 145 | ```javascript 146 | // All keys in serviceFunctions will be in the props of the react component. 147 | // Each entry is a function that takes the simpler-redux store as a parameter 148 | // and then performs state transitions by using store.setRState. 149 | // The functions may be synchronous or asynchronous and thunking middleware 150 | // is not required. 151 | export const serviceFunctions = { 152 | // Below is an example of an synchronous call. 153 | increment: store => store.setRState(reducerKey, { counter: store.getRState(reducerKey).counter + 1 }, 'increment'), 154 | // Below is an example of an asynchronous call. 155 | asyncCall: store => { 156 | store.setRState(reducerKey, { isBusy: true }) 157 | someAsyncCall( 158 | data => store.setRState(reducerKey, { isBusy: false, data }) 159 | ) 160 | }, 161 | // Below handles the componentDidMount react life cycle event. 162 | componentDidMount: store => handleLoadData(store) 163 | } 164 | ``` 165 | - `mergeProps` - Redux connect `mergeProps` parameter. 166 | - `reduxOptions` - Redux connect `options` parameter. 167 | - `storeIsDefinedCallback` - Once the module's react component render is called for the first time, this callback will be invoked with the simpler redux store and the `stateAccessors` function as parameters. So it will be called before any possible user interactions. 168 | - `noStoreParameterOnServiceFunctions` - Used in conjunction with `storeIsDefinedCallback`. This will cause simpler-redux to not add the store parameter when calling the service functions. This should not be used for shared modules. 169 | - `reducerKey` - Used only in conjunction with `initialUIState`. When both of these are specified simpler-redux will build the mapStateToProps function directly from the keys in `initialUIState`. 170 | - `initialUIState` - Used only in conjunction with `reducerKey`. When both of these are specified simpler-redux will build the mapStateToProps function directly from the keys in `initialUIState`. This can be only used when your react component props only needs state from the declarative state definition `initialUIState`. Do not specify this and `selectors`. If this is not specified and `initialState` is then `initialUIState` will default to `initialState`. 171 | - `initialState` - Used only in conjunction with `reducerKey`. When both of these are specified simpler-redux will build the reducer that manages the state at `reducerKey`. If this is not specified and `initialUIState`is then `initialState` will default to `initialUIState`. 172 | - `isDynamicReducer` - Supports dynamic loading of the component and the reducer. If you specify the `rootReducersObject` parameter of your `registerSimplerRedux` call then simpler-redux automatically assumes isDynamicReducer is true. Set this to false if you want to manually load your reducer in the global reducers object for a specific react component. 173 | - `selectorList` - An array of {selectors, keylist[list of selector keys]}. This allows combining selectors from different modules into one in order to build a mapStateToProps that includes key/values from other reducers keys including the component reducer selectors. If you specify keylist then you can include only a subset of the selectors indtead of all of them. 174 | Below is an example. 175 | ```javascript 176 | import { selectors as counterSelectors } from './Counter' 177 | 178 | export const selectorList = [ 179 | {counterSelectors, ['counter']} 180 | ] 181 | ``` 182 | - `serviceFunctionList` - An array of {serviceFunctions, keylist[list of serviceFunctions keys], withStore}. This allows combining serviceFunctions from different modules into one in order to build a mapDispatchToProps that includes key/values from other module serviceFunctions. The keylist allows you to select only a subset of the associated service functions. The withStore set to true will cause the store to be the first parameter for all the service functions when called with the UI parameters following after. 183 | Below is an example. 184 | ```javascript 185 | import { serviceFunctions as counterServiceFunctions } from './Counter' 186 | 187 | export const serviceFunctionList = [ 188 | {counterServiceFunctions, ['increment']} 189 | ] 190 | ``` 191 | 192 | **Return Value** 193 | None 194 | 195 | #### _stateAccessors_ 196 | **Description** 197 | `stateAccessors(store, reducerKey[, initialState])` - Returns a `setState`, `getState` and `reducerState` in an object that provides easier state management without having to provide a store or reducerKey. If you pass in initialState then it will also return reducerState which is a proxy to the reducerKey state. For getting state, reducerState.key is equivalent to getState().key. For setting state, reducerState.key = 1 is equivalent to setState({key: 1}). It is really just syntactic sugar for the getState and setState functions. The funciton stateAccessors should only be called as below in the storeIsDefinedCallback described above. Make sure to supply storeIsDefinedCallback to connectLifeCycleComponentWithStore or connectWithStore. 198 | ```javascript 199 | let setState, getState, reducerState 200 | export const storeIsDefinedCallback = (store, stateAccessors) => 201 | ({setState, getState, reducerState} = stateAccessors(store, reducerKey, initialState)) 202 | 203 | // Below are examples of using the functions. 204 | let counter = getState().counter 205 | setState({isBusy: true}) 206 | reducerState.counter++ 207 | counter = reducerState.counter 208 | reducerState.counter = 10 209 | ``` 210 | #### _buildSelectorsFromUIState_ 211 | **Description** 212 | `buildSelectorsFromUIState(reducerKey, uiInitialState) ` - Builds a selectors object from uiInitialState and returns that object. 213 | **Parameters** 214 | - `reducerKey` - Redux reducer key name. 215 | - `uiInitialState` - uiInitialState should only contain keys that you want in the props of the react component. 216 | 217 | **Return Value** 218 | A UI selectors object. 219 | 220 | ### Similarity to react's component state management 221 | - State transition 222 | - React - `this.setState(objToMerge)`. React does not guarantee that the state changes are applied immediately. 223 | - simpler-redux - `simplerReduxStore.setRState(reducerKey, objToMerge, [type])`. This is an immediate command. 224 | - Get state 225 | - React - `let x = this.state.x` 226 | - simpler-redux - `let x = simplerReduxStore.getRState(reducerKey)` 227 | 228 | ### Shared state management support 229 | With shared state management, you can write one module that performs state management and then any simpler-redux MVC module code can simply include the functionality into the initialState, selectors and serviceFunctions by using an import of the shared modules functions and writing just three lines of code. 230 | #### _makeSharedModuleKeyName_ 231 | **Description** 232 | `makeSharedModuleKeyName(key, options)` - In order to avoid state and prop key collisions, this function returns the concatenation of the `key` followed by `options.id`. 233 | **Parameters** 234 | - `key` - The key that is used by the shared module as either a state key or service function key. 235 | - `options` - An object with a key suffix at the id key in the object. For example, `options.key ='AsyncModule'`. 236 | - 237 | **Return Value** 238 | The return value is the concatenation of the `key` followed by `options.id`. 239 | 240 | #### _createModuleData_ 241 | **Description** 242 | `createModuleData(store, reducerKey[, initialState])` - Creates redux module data to replace typical module data. This way, you can manage and track changes to your module data through redux logging software. 243 | 244 | **Return Value** 245 | Returns a `setState`, `getState` and `reducerState` in an object. 246 | 247 | ### Sample usage of simpler-redux 248 | The model code is located at `src/Counter/model.js. This code manages the state for the reducerKey. It also is responsible for performing asynchronous operations and managing the state through those transactions. So, the model code contains the state management and the business logic. 249 | ```javascript 250 | export const reducerKey = 'counter.1' 251 | const counterKey = 'counter' 252 | 253 | const initialState = { 254 | [counterKey]: 0 255 | } 256 | // Selectors always take in the entire redux state object. 257 | // So, state[reducerKey] is the state object at the reducerKey. 258 | // simpler-redux constructs a mapStateToProps from this object and 259 | // calls redux connect using the constructed mapStateToProps. 260 | // The keys below will be in the props of the react component. 261 | export const selectors = { 262 | [counterKey]: state => 263 | state[reducerKey][counterKey] 264 | } 265 | // serviceFunctions always takes the simpler-redux enhanced redux store as the first parameter. 266 | // Other parameters supplied by the react component come after. 267 | // simpler-redux constructs a mapDispatchToProps from this object and 268 | // calls redux connect using the constructed mapDispatchToProps. 269 | // The keys below will be in the props of the react conponent and 270 | // whenever the UI call the function in the props like say increment, 271 | // the increment function below will called with the store as a parameter 272 | // and the UI parameters follow after that. 273 | export const serviceFunctions = { 274 | increment: store => 275 | store.setRState(reducerKey, { [counterKey]: store.getRState(reducerKey)[counterKey] + 1 }, 'increment') 276 | } 277 | ``` 278 | The View code is located at src/Counter/view.js. It should only display information received in the props and call functions supplied by the props. This code is responsible for display only. Any functional behavior should be supplied by the model to the controller and then into the props of the view where is is simply called by the view. 279 | ```javascript 280 | import React from 'react' 281 | 282 | export default ({counter, increment}) => 283 |
284 |
Counter: {counter}
285 | 286 |
287 | ``` 288 | The controller code is located at `src/Counter/index.js`. This is generally the same for all controllers so it is basically a copy and paste. 289 | ```javascript 290 | import { connectWithStore, generalReducer } from 'simpler-redux' 291 | import uiComponent from './view/view' 292 | import * as modelDefinition from './model/model' 293 | 294 | // Use connectLifeCycleComponentWithStore to handle react life cycle events in the serviceFunctions object in the model code. 295 | export default connectWithStore({ uiComponent, ...modelDefinition }) 296 | export const reducerKey = modelDefinition.reducerKey 297 | export const reducer = generalReducer(reducerKey, modelDefinition.initialState) 298 | // Simpler-redux builds the reducer for you. Add reducerKey and reducer to your global reducer object. 299 | ``` 300 | Note that the above controller code does not contain any intelligence. Its purpose is simply to connect the model code to the view code without knowing anything about the details of either's implementions. Therefore, this technique removes any side effects of changing the model code or underlying state. Also, this code can be copy and pasted for any component because it does the same thing for most simpler-redux component modules. 301 | The src/Counter/index.js code exports features of the simpler-redux MVC module to the outside. 302 | 303 | The reducer code located at `src/reducers.js`. 304 | ```javascript 305 | import { reducerKey as counterReducerKey, reducer as counterReducer } from './Counter' 306 | 307 | export default { 308 | [counterReducerKey]: counterReducer 309 | } 310 | ``` 311 | The redux store code is located at `src/reduxstore.js`. 312 | ```javascript 313 | import reducersObject from './reducers' 314 | import { createStore, combineReducers } from 'redux' 315 | import { registerSimplerRedux } from 'simpler-redux' 316 | 317 | export default registerSimplerRedux(createStore(combineReducers(reducersObject))) 318 | ``` 319 | Last, below is the App code located at `src/App.jsx`. 320 | ```javascript 321 | import React from 'react' 322 | import { Provider } from 'react-redux' 323 | import store from './reduxstore' 324 | import Counter from './Counter' 325 | 326 | export default () => 327 | 328 | 329 | 330 | ``` 331 | ### Sample usage of simpler-redux shared state management code 332 | ##### Shared module 333 | Below is the style of code that would be written for shared state management. The state keys and service function keys all will have an input module id appended to the end of the keys. This prevents key collisions in the props of react components and keys in the reducer key state. 334 | ```javascript 335 | import { makeSharedModuleKeyName } from 'simpler-redux' 336 | const counterKey = 'counter' 337 | const incrementKey = 'increment' 338 | 339 | const makeCounterKey = options => 340 | makeSharedModuleKeyName(counterKey, options) 341 | 342 | // The key below is 'counter' + options.id. This way the consumer of the 343 | // shared module can make the keys what they want to avoid key collisions. 344 | export const getInitialState = options => ({ 345 | [makeCounterKey(options)]: 0 346 | }) 347 | 348 | // The key below is 'counter' + options.id 349 | export const getSelectors = (reducerKey, options) => { 350 | return { 351 | [makeCounterKey(options)]: state => state[reducerKey][makeCounterKey(options)] 352 | } 353 | } 354 | 355 | // All shared module serviceFunction functions have the following signature. 356 | export const getServiceFunctions = (reducerKey, options) => { 357 | return { 358 | [makeSharedModuleKeyName(incrementKey, options)]: store => { 359 | // Change the state at 'counter' + options.id 360 | store.setRState( 361 | reducerKey, 362 | { [makeCounterKey(options)]: store.getRState(reducerKey)[makeCounterKey(options)] + 1 } 363 | ) 364 | } 365 | } 366 | } 367 | ``` 368 | ##### Consumer module 369 | Next is the code that consumes the above shared module. 370 | ```javascript 371 | import { generalReducer } from 'simpler-redux' 372 | import { 373 | getServiceFunctions as sharedCounterGetServiceFunctions, 374 | getSelectors as sharedCounterGetSelectors, 375 | getInitialState as sharedCounterInitialState 376 | } from '../SharedModel/Counter' 377 | 378 | export const reducerKey = 'counter.1' 379 | const asyncModuleId = 'CounterModule' 380 | const baseOptions = { id: asyncModuleId } 381 | 382 | const initialState = { 383 | ...sharedCounterInitialState(baseOptions) 384 | } 385 | 386 | export const selectors = { 387 | ...sharedCounterGetSelectors(reducerKey, baseOptions) 388 | } 389 | 390 | export const serviceFunctions = { 391 | ...sharedCounterGetServiceFunctions(reducerKey, baseOptions) 392 | } 393 | 394 | export const reducer = generalReducer(reducerKey, initialState) 395 | ``` 396 | Here are the advantages of the above implementation. 397 | 1. No additional reducers are required which would degrade performance. 398 | 2. The consumer does not participate in the state management. 399 | 3. Your organization can collect this sharable state management code into libraries so that you do not have to repeat yourself in various projects. 400 | 4. The sharable code and the consumer code share the same paradigm (initialState, selectors, serviceFunctions). Therefore, the code is easy to understand. 401 | 5. This technique is simpler and easier to maintain as compared to the shared state management of redux. 402 | 403 | So with simpler-redux state management you only need to think about three simple concepts: 404 | 1. initialState to initialize the state at the reducer key. 405 | 2. selectors to get slices of the current state at the reducer key. 406 | 3. serviceFunctions to state transition slices of the current state at the reducer key or call asynchronous functions. 407 | 408 | Note that simpler-redux also converts react-redux into an MVC implementation for the react UI. As most will know, MVC is the correct way to implement user interface code. In the simpler-redux case, MVC is as follows. 409 | 1. The view is the stateless react component. 410 | 2. The model is the state management and service function code. 411 | 3. The controller is responsible for providing the connections between the view and model. 412 | -------------------------------------------------------------------------------- /devtools/devServer.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console: 0*/ 2 | const webpack = require('webpack'); 3 | const WebpackDevServer = require('webpack-dev-server'); 4 | const config = require('./webpack.config.dev'); 5 | 6 | const port = process.env.npm_package_config_port || 3000; 7 | const host = process.env.npm_package_config_host || 'localhost'; 8 | 9 | new WebpackDevServer(webpack(config), { 10 | publicPath: config.output.publicPath, 11 | hot: true, 12 | historyApiFallback: true, 13 | stats: { 14 | colors: true, 15 | chunks: false, 16 | 'errors-only': true 17 | } 18 | }).listen(port, host, function (err) { 19 | if (err) { 20 | console.log(err); 21 | } 22 | 23 | console.log(`Listening at http://${host}:${port}/`); 24 | }); -------------------------------------------------------------------------------- /devtools/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | Simpler Redux 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

13 |

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /devtools/webpack.config.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const libraryName = 'simpler-redux' 4 | 5 | // Entry point for your library. This is assumed to be in the src directory at the root level. 6 | const libEntryJs = 'index.js' 7 | 8 | // Entry point for your minimized build. This is assumed to be in the src directory at the root level. 9 | const buildEntryJs = 'index.js' 10 | 11 | // List of modules you require for your library and do not want them contained in your own 12 | // library module. For example, you would not want react code contained in your library so that 13 | // would be in the list below. But you may want an Object.assign polyfill in your library. So, 14 | // do not list object-assign and that will be included in your library. 15 | // The key item is the library name and the second is how you will refer to it in 16 | // your code. 17 | const externalsLibMin = { 18 | 'redux': 'Redux', 19 | 'react': 'React' 20 | } 21 | 22 | const externalsLibLib = { 23 | 'redux': 'redux', 24 | 'react': 'react', 25 | 'react-redux': 'react-redux', 26 | 'prop-types': 'prop-types' 27 | } 28 | 29 | /* 30 | This is a list of modules used to create a dll library. 31 | Important for faster compiling since these rarely change. 32 | Note: If you add or subtract from this list or update any modules in the list, pull up the task list 33 | and select "Builddll Development". Otherwise, run the command npm run builddll:dev. 34 | */ 35 | const dllModules = [ 36 | 'redux', 37 | 'react', 38 | 'react-dom', 39 | 'react-redux', 40 | 'object-assign' 41 | ] 42 | 43 | const config = { 44 | sourceDir: 'src', // Source directory. 45 | buildDir: 'dist', // Build directory 46 | libDir: 'lib', // Library build directory 47 | libEntryJs, 48 | buildEntryJs, 49 | libraryName: libraryName, 50 | bundleName: 'bundle', // For development debug only. 51 | testEntryJs: 'test.dev/index.dev.js', // For development debug only. 52 | htmlTemplate: 'index.ejs', // For development debug only. 53 | 54 | // List of modules you require for your library and do not want them contained in your own 55 | // library module. For example, you would not want react code contained in your library so that 56 | // would be in the list below. But you may want an Object.assign polyfill in your library. 57 | // The key item is the library name and the second is how you will refer to it in 58 | // your code. 59 | externalsLibMin, 60 | externalsLibLib, 61 | 62 | dllDir: 'devdll', // Build directory for the the development dll library. 63 | dllBundleName: 'dllbundle', 64 | dllModules, 65 | htmlDevTemplate: 'index.dev.ejs', // Used for the development html template. This is built. 66 | 67 | libraryTarget: 'commonjs2', 68 | module: { 69 | loaders: [ 70 | { 71 | test: /\.js$/, 72 | exclude: /node_modules/, 73 | loader: 'babel-loader' 74 | } 75 | ] 76 | }, 77 | node_modulesPath: 'node_modules', 78 | resolveExtensions: ['.js', '.jsx'] 79 | } 80 | 81 | config.basePath = process.cwd() 82 | 83 | // At this time, the source directory must be directly below the vscode directory. 84 | config.absoluteSourcePath = path.join(config.basePath, config.sourceDir) 85 | 86 | // At this time, the build directory must be directly below the vscode directory. 87 | config.absoluteBuildPath = path.join(config.basePath, config.buildDir) 88 | 89 | // At this time, the lib directory must be directly below the vscode directory. 90 | config.absoluteLibPath = path.join(config.basePath, config.libDir) 91 | 92 | // At this time, the dll directory must be directly below the vscode directory. 93 | config.absoluteDllPath = path.join(config.basePath, config.dllDir) 94 | 95 | config.absoluteDevToolsPath = path.join(config.basePath, 'devtools') 96 | 97 | config.resolveEntry = { 98 | modules: [ 99 | config.absoluteSourcePath, 100 | config.node_modulesPath 101 | ], 102 | extensions: config.resolveExtensions 103 | } 104 | 105 | module.exports = config 106 | -------------------------------------------------------------------------------- /devtools/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const config = require('./webpack.config.config.js') 5 | 6 | const devConfig = { 7 | devtool: 'cheap-module-source-map', 8 | watch: true, 9 | entry: [ 10 | `webpack-dev-server/client?http://${process.env.npm_package_config_host}:${process.env.npm_package_config_port}`, 11 | path.join(config.basePath, config.testEntryJs) 12 | ], 13 | output: { 14 | path: config.absoluteBuildPath, 15 | filename: `${config.bundleName}.js` 16 | }, 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | '__DEV__': true, 20 | 'process.env': { 21 | 'NODE_ENV': JSON.stringify('development') 22 | } 23 | }), 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NamedModulesPlugin() 26 | ], 27 | module: config.module, 28 | resolve: config.resolveEntry 29 | } 30 | 31 | if (config.dllModules.length > 0) { 32 | devConfig.plugins.push( 33 | new webpack.DllReferencePlugin({ 34 | context: config.absoluteDllPath, 35 | manifest: require(path.join(config.absoluteDllPath, `${config.dllBundleName}.json`)) 36 | }) 37 | ) 38 | devConfig.plugins.push( 39 | new HtmlWebpackPlugin({ 40 | template: config.htmlDevTemplate, 41 | inject: 'body' 42 | }) 43 | ) 44 | } else { 45 | devConfig.plugins.push( 46 | new HtmlWebpackPlugin({ 47 | template: path.join(config.absoluteDevToolsPath, config.htmlTemplate), 48 | inject: 'body' 49 | }) 50 | ) 51 | } 52 | 53 | module.exports = devConfig 54 | -------------------------------------------------------------------------------- /devtools/webpack.config.dist.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const config = require('./webpack.config.config') 3 | 4 | module.exports = { 5 | entry: path.join(config.absoluteSourcePath, config.buildEntryJs), 6 | output: { 7 | path: config.absoluteBuildPath, 8 | filename: `${config.libraryName}.js` 9 | }, 10 | externals: config.externalsLibMin, 11 | module: config.module 12 | } 13 | -------------------------------------------------------------------------------- /devtools/webpack.config.dist.min.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const config = require('./webpack.config.config'); 3 | const webpack = require('webpack'); 4 | 5 | module.exports = { 6 | entry: path.join(config.absoluteBuildPath, `${config.libraryName}.js`), 7 | output: { 8 | path: config.absoluteBuildPath, 9 | filename: `${config.libraryName}.min.js`, 10 | }, 11 | plugins: [ 12 | new webpack.DefinePlugin({ 13 | '__DEV__': false, 14 | 'process.env': { 15 | 'NODE_ENV': JSON.stringify('production') 16 | } 17 | }), 18 | new webpack.optimize.UglifyJsPlugin({ 19 | compressor: { 20 | warnings: false 21 | }, 22 | output: { 23 | comments: false 24 | } 25 | }) 26 | ], 27 | }; 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /devtools/webpack.config.dlllib.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const config = require('./webpack.config.config') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | 6 | module.exports = { 7 | entry: { 8 | dllModules: config.dllModules 9 | }, 10 | 11 | output: { 12 | filename: `${config.dllBundleName}.js`, 13 | path: config.absoluteDllPath, 14 | library: config.dllBundleName 15 | }, 16 | 17 | plugins: [ 18 | new webpack.DllPlugin({ 19 | context: config.absoluteDllPath, 20 | name: config.dllBundleName, 21 | path: path.join(config.absoluteDllPath, `${config.dllBundleName}.json`) 22 | }), 23 | new HtmlWebpackPlugin({ 24 | template: path.join(config.absoluteDevToolsPath, config.htmlTemplate), 25 | filename: path.join(config.basePath, config.htmlDevTemplate), 26 | inject: 'body', 27 | hash: true 28 | }) 29 | ], 30 | resolve: config.resolveEntry 31 | } 32 | -------------------------------------------------------------------------------- /devtools/webpack.config.lib.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const config = require('./webpack.config.config') 3 | 4 | module.exports = { 5 | entry: path.join(config.absoluteSourcePath, config.libEntryJs), 6 | output: { 7 | path: config.absoluteLibPath, 8 | filename: `${config.libraryName}.js`, 9 | libraryTarget: config.libraryTarget 10 | }, 11 | externals: config.externalsLibLib, 12 | module: config.module 13 | } 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true 4 | }, 5 | "exclude": [ 6 | "typings/main.d.ts", 7 | "typings/main", 8 | "node_modules" 9 | ] 10 | } -------------------------------------------------------------------------------- /lib/simpler-redux.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) { 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ } 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // define getter function for harmony exports 38 | /******/ __webpack_require__.d = function(exports, name, getter) { 39 | /******/ if(!__webpack_require__.o(exports, name)) { 40 | /******/ Object.defineProperty(exports, name, { 41 | /******/ configurable: false, 42 | /******/ enumerable: true, 43 | /******/ get: getter 44 | /******/ }); 45 | /******/ } 46 | /******/ }; 47 | /******/ 48 | /******/ // getDefaultExport function for compatibility with non-harmony modules 49 | /******/ __webpack_require__.n = function(module) { 50 | /******/ var getter = module && module.__esModule ? 51 | /******/ function getDefault() { return module['default']; } : 52 | /******/ function getModuleExports() { return module; }; 53 | /******/ __webpack_require__.d(getter, 'a', getter); 54 | /******/ return getter; 55 | /******/ }; 56 | /******/ 57 | /******/ // Object.prototype.hasOwnProperty.call 58 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 59 | /******/ 60 | /******/ // __webpack_public_path__ 61 | /******/ __webpack_require__.p = ""; 62 | /******/ 63 | /******/ // Load entry module and return exports 64 | /******/ return __webpack_require__(__webpack_require__.s = 6); 65 | /******/ }) 66 | /************************************************************************/ 67 | /******/ ([ 68 | /* 0 */ 69 | /***/ (function(module, exports, __webpack_require__) { 70 | 71 | "use strict"; 72 | 73 | 74 | if (true) { 75 | module.exports = __webpack_require__(8); 76 | } else { 77 | module.exports = require('./dist/react-hot-loader.development.js'); 78 | } 79 | 80 | 81 | /***/ }), 82 | /* 1 */ 83 | /***/ (function(module, exports) { 84 | 85 | module.exports = function(module) { 86 | if(!module.webpackPolyfill) { 87 | module.deprecate = function() {}; 88 | module.paths = []; 89 | // module.parent = undefined by default 90 | if(!module.children) module.children = []; 91 | Object.defineProperty(module, "loaded", { 92 | enumerable: true, 93 | get: function() { 94 | return module.l; 95 | } 96 | }); 97 | Object.defineProperty(module, "id", { 98 | enumerable: true, 99 | get: function() { 100 | return module.i; 101 | } 102 | }); 103 | module.webpackPolyfill = 1; 104 | } 105 | return module; 106 | }; 107 | 108 | 109 | /***/ }), 110 | /* 2 */ 111 | /***/ (function(module, exports, __webpack_require__) { 112 | 113 | "use strict"; 114 | /* WEBPACK VAR INJECTION */(function(module, process) {Object.defineProperty(exports, "__esModule", { value: true });exports.createModuleData = exports.stateAccessors = exports.reducersPreloadedState = exports.makeSharedModuleKeyName = exports.setStateFunction = exports.getStateFunction = exports.createStore = exports.registerSimplerRedux = exports.generalReducer = exports.srOptions = undefined;var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;}; 115 | 116 | 117 | var _redux = __webpack_require__(7); 118 | var _util = __webpack_require__(4); 119 | var _proxy = __webpack_require__(9);var _proxy2 = _interopRequireDefault(_proxy);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}(function () {var enterModule = __webpack_require__(0).enterModule;enterModule && enterModule(module);})();function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;} /* 120 | Written by Andrew Banks. MIT license. 121 | */var simplerReduxReducerKey = '@@@@@srReducerKey'; 122 | var simplerReduxObjectToMergeKey = '@@@@@srObjectToMergeKey'; 123 | 124 | var objectType = function objectType(obj) {return Object.prototype.toString.call(obj).slice(8, -1);}; 125 | var isObjectType = function isObjectType(obj) {return objectType(obj) === 'Object';}; 126 | 127 | var srOptions = exports.srOptions = void 0; 128 | 129 | var makeSetRState = function makeSetRState(reduxStore) { 130 | return function (reducerKey, objToMerge, type) {var _reduxStore$dispatch; 131 | if (type === undefined) { 132 | type = reducerKey; 133 | } 134 | if (process.env.NODE_ENV !== 'production') { 135 | if (typeof reducerKey !== 'string') { 136 | throw new Error('setRState: The first argument must be a string.'); 137 | } 138 | if (!isObjectType(objToMerge)) { 139 | throw new Error('setRState: The second argument must be a primitive object type.'); 140 | } 141 | if (typeof type !== 'string') { 142 | throw new Error('setRState: The third argument must be a string.'); 143 | } 144 | } 145 | 146 | reduxStore.dispatch((_reduxStore$dispatch = {}, _defineProperty(_reduxStore$dispatch, 147 | simplerReduxReducerKey, reducerKey), _defineProperty(_reduxStore$dispatch, 148 | simplerReduxObjectToMergeKey, objToMerge), _defineProperty(_reduxStore$dispatch, 'type', 149 | type), _reduxStore$dispatch)); 150 | 151 | 152 | reduxStore.listeners.forEach(function (listenerObj) { 153 | listenerObj.listener(reducerKey, objToMerge, type); 154 | }); 155 | }; 156 | }; 157 | 158 | var makeGetRState = function makeGetRState(reduxStore) { 159 | return function (reducerKey) { 160 | var state = reduxStore.getState()[reducerKey]; 161 | if (process.env.NODE_ENV !== 'production') { 162 | if (typeof reducerKey !== 'string') { 163 | throw new Error('getRState: The first argument must be a string.'); 164 | } 165 | if (state === undefined) { 166 | throw new Error('The reducerKey state at ' + reducerKey + ' is undefined. Did you forget to export an initialUIState or initialState from your model code?'); 167 | } 168 | } 169 | return state; 170 | }; 171 | }; 172 | 173 | // These listeners do what redux subscribers should have done. It gives the reducerKey being modified 174 | // so that a listener can quickly decide if it is concerned about changes in that reducerKey. 175 | var addListener = function addListener(store) { 176 | return function (listener) { 177 | if (process.env.NODE_ENV !== 'production') { 178 | if (typeof listener !== 'function') { 179 | throw new Error('addListener: The first argument must be a function.'); 180 | } 181 | } 182 | var id = store.listenerId++; 183 | store.listeners.push({ listener: listener, id: id }); 184 | // Return a function that will remove this listener. 185 | return function () { 186 | var i = 0; 187 | for (; i < store.listeners.length && store.listeners[i].id !== id; ++i) {} 188 | if (i < store.listeners.length) { 189 | store.listeners.splice(i, 1); 190 | } 191 | }; 192 | }; 193 | }; 194 | 195 | // 196 | // Call this to generate your reducer. 197 | // 198 | var generalReducer = exports.generalReducer = function generalReducer(reducerKey, initialState) { 199 | if (process.env.NODE_ENV !== 'production') { 200 | if (reducerKey === undefined) { 201 | throw new Error('generalReducer: reducerKey must be defined.'); 202 | } 203 | if (initialState === undefined) { 204 | throw new Error('generalReducer: initialState must be defined.'); 205 | } 206 | } 207 | initialState = _extends({}, initialState); 208 | return function () {var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;var action = arguments[1]; 209 | if (action[simplerReduxReducerKey] === reducerKey) { 210 | return _extends({}, state, action[simplerReduxObjectToMergeKey]); 211 | } 212 | return state; 213 | }; 214 | }; 215 | 216 | // Allows dynamic loading of simpler redux mvc components and their associated reducers. 217 | var buildAddReducer = function buildAddReducer(store, preloadedState) { 218 | return function (reducerKey, initialState) { 219 | if (process.env.NODE_ENV !== 'production') { 220 | if (reducerKey === undefined) { 221 | throw new Error('addReducer: The first argument (reducerKey) must be defined.'); 222 | } 223 | if (initialState === undefined) { 224 | throw new Error('addReducer: The second argument (initialState) must be defined.'); 225 | } 226 | } 227 | // First load the initial state for the reducer. 228 | var state = _extends({}, initialState); 229 | // Then load the preloadedState for the keys that exist in the initial state. 230 | // Therefore, keys in preloadedState[reducerKey] that are not part of the current 231 | // state shape in initialState are considered deprecated and are ignored. 232 | var preloadedStateAtReducerKey = preloadedState[reducerKey]; 233 | if (preloadedStateAtReducerKey !== undefined) { 234 | Object.keys(preloadedStateAtReducerKey).forEach(function (key) { 235 | if (state[key] !== undefined) { 236 | state[key] = preloadedStateAtReducerKey[key]; 237 | } 238 | }); 239 | } 240 | // One reducer with no typical redux reducers. 241 | if (store.isOneReducer) { 242 | var currentState = store.getState(); 243 | // Do not use initialState on HMR. 244 | if (currentState === undefined || currentState[reducerKey] === undefined) { 245 | // Set the initialState state at the reducerKey. 246 | store.setRState(reducerKey, state); 247 | } 248 | return; 249 | } 250 | // Generate the reducer for reducerKey. 251 | var reducer = generalReducer(reducerKey, state); 252 | // Add the reducer to the current list. 253 | store.currentReducersObject = _extends({}, store.currentReducersObject, _defineProperty({}, reducerKey, reducer)); 254 | // Replace the redux reducers with the new list. 255 | store.replaceReducer((0, _redux.combineReducers)(store.currentReducersObject)); 256 | }; 257 | }; 258 | 259 | // 260 | // This must be called with the redux store as a parameter after a createStore. 261 | // Then use the return of this function in the react-redux Provider element as the store. 262 | // If you pass in a rootReducersObject then you can use simpler-redux dynamic loading of reducers. 263 | // Note: rootReducersObject is the actual reducers object and not the combineReducers output. 264 | // If you want only dynamic reducers, use state => state (null reducer) for the redux createStore reducer 265 | // and { } as the rootReducersObject for the call below. 266 | // If you use rootReducersObject then you should also pass in preloadedState (if it exists). 267 | // options 268 | // 1) isDynamicReducer - Default to dynamic reducer loading for react components so that you do not have to specify isDynamicReducer in each component module. 269 | // 270 | var registerSimplerRedux = exports.registerSimplerRedux = function registerSimplerRedux( 271 | reduxStore, 272 | rootReducersObject) 273 | 274 | 275 | {var preloadedState = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; 276 | exports.srOptions = srOptions = (0, _util.defaultOptions)(options); 277 | var wrappedReduxStore = Object.create(reduxStore); 278 | wrappedReduxStore.setRState = makeSetRState(wrappedReduxStore); 279 | wrappedReduxStore.getRState = makeGetRState(wrappedReduxStore); 280 | wrappedReduxStore.addListener = addListener(wrappedReduxStore); 281 | wrappedReduxStore.listenerId = 0; 282 | wrappedReduxStore.listeners = []; 283 | // Support for dynamic reducer loading. 284 | if (rootReducersObject !== undefined) { 285 | wrappedReduxStore.isDynamicReducerLoading = function () {return true;}; 286 | wrappedReduxStore.currentReducersObject = _extends({}, rootReducersObject); 287 | wrappedReduxStore.addReducer = buildAddReducer(wrappedReduxStore, _extends({}, preloadedState)); 288 | } else { 289 | wrappedReduxStore.isDynamicReducerLoading = function () {return false;}; 290 | if (process.env.NODE_ENV !== 'production') { 291 | wrappedReduxStore.addReducer = function () { 292 | throw new Error('To call addReducer, you must specify a rootReducersObject in the 2nd argument of registerSimplerRedux which can be just {}.'); 293 | }; 294 | } 295 | } 296 | return wrappedReduxStore; 297 | }; 298 | 299 | // 300 | // One reducer is not compatible with existing redux code. Must be all simpler-redux. 301 | // This is the only reducer called for all state transitions. 302 | // 303 | var oneReducer = function oneReducer() {var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};var action = arguments[1]; 304 | var reducerKey = action[simplerReduxReducerKey]; 305 | var objToMerge = action[simplerReduxObjectToMergeKey]; 306 | // This is some redux thing, not from our setRState. 307 | if (reducerKey === undefined) { 308 | return state; 309 | } 310 | // Must change the upper level redux state pointer or redux does not recognize a state change. 311 | state = _extends({}, state); 312 | // Merge the incoming reducerKey state at the reducerKey 313 | state[reducerKey] = _extends({}, state[reducerKey], objToMerge); 314 | return state; 315 | }; 316 | 317 | // This cannot be used with redux reducers. 318 | var createStore = exports.createStore = function createStore(preloadedState, enhancer) {var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 319 | var reduxStore = (0, _redux.createStore)( 320 | oneReducer, 321 | preloadedState, 322 | enhancer); 323 | 324 | var wrappedReduxStore = registerSimplerRedux( 325 | reduxStore, 326 | {}, 327 | preloadedState, 328 | options); 329 | 330 | wrappedReduxStore.isOneReducer = true; 331 | return wrappedReduxStore; 332 | }; 333 | 334 | // This makes it easier to access reducerKey state previously given a reducerKey. 335 | var getStateFunction = exports.getStateFunction = function getStateFunction(reducerKey) {return ( 336 | function (state, key) {return ( 337 | state[reducerKey][key]);});}; 338 | 339 | // This makes it easier to set reducerKey state previously given a reducerKey. 340 | var setStateFunction = exports.setStateFunction = function setStateFunction(reducerKey) {return ( 341 | function (store, mergeState, type) {return ( 342 | store.setRState(reducerKey, mergeState, type));});}; 343 | 344 | // Use this to generate shared module keys. 345 | var makeSharedModuleKeyName = exports.makeSharedModuleKeyName = function makeSharedModuleKeyName(key, options) {return '' + 346 | key + options.id;}; 347 | 348 | // Sifts out dynamically loaded reducer keys from the preloaded state in order to avoid 349 | // a redux warning. Use the return of this function to pass into the redux createStore. 350 | var reducersPreloadedState = exports.reducersPreloadedState = function reducersPreloadedState(reducersObject, preloadedState) {return ( 351 | Object.keys(preloadedState).reduce(function (obj, key) { 352 | if (reducersObject[key] !== undefined) { 353 | obj[key] = preloadedState[key]; 354 | } 355 | return obj; 356 | }, {}));}; 357 | 358 | var getState = function getState(store, reducerKey) {return ( 359 | function () {return store.getRState(reducerKey);});}; 360 | 361 | var setState = function setState(store, reducerKey) {return ( 362 | function (mergeState, type) { 363 | if (process.env.NODE_ENV !== 'production') { 364 | if (!isObjectType(mergeState)) { 365 | throw new Error('setState: The first argument must be a primitive object type.'); 366 | } 367 | } 368 | store.setRState(reducerKey, mergeState, type); 369 | });}; 370 | 371 | // 372 | // Only call this in the storeIsDefinedCallback sent into connectWithStore above. 373 | // Use the store parameter provided in connectWithStore along with the reducerKey 374 | // in the module. 375 | // 376 | var stateAccessors = exports.stateAccessors = function stateAccessors(store, reducerKey, initialState) { 377 | if (process.env.NODE_ENV !== 'production') { 378 | if (store === undefined) { 379 | throw new Error('The first parameter (store) to stateAccessors must be defined.'); 380 | } 381 | if (typeof reducerKey !== 'string') { 382 | throw new Error('The second parameter (reducerKey) to stateAccessors must be a string.'); 383 | } 384 | } 385 | var ret = { 386 | getState: getState(store, reducerKey), 387 | setState: setState(store, reducerKey) }; 388 | 389 | 390 | if (initialState !== undefined) { 391 | ret.reducerState = (0, _proxy2.default)(store, reducerKey, initialState); 392 | } 393 | 394 | return ret; 395 | }; 396 | 397 | // Creates general purpose module data under a redux reducerKey. 398 | // A redux store must be imported from your redux createStore module for the argument below. 399 | var createModuleData = exports.createModuleData = function createModuleData(store, reducerKey, initialState) { 400 | if (process.env.NODE_ENV !== 'production') { 401 | if (!store.isDynamicReducerLoading()) { 402 | throw new Error('To call createModuleData, you must specify a rootReducersObject in the 2nd argument of registerSimplerRedux which can be just {}.'); 403 | } 404 | if (store === undefined) { 405 | throw new Error('The first parameter (store) to createModuleData must be defined.'); 406 | } 407 | if (typeof reducerKey !== 'string') { 408 | throw new Error('The seccond parameter (reducerKey) to createModuleData must a string.'); 409 | } 410 | if (initialState === undefined) { 411 | throw new Error('The third parameter (initialState) to createModuleData must be defined.'); 412 | } 413 | } 414 | store.addReducer(reducerKey, initialState); 415 | return stateAccessors(store, reducerKey, initialState); 416 | };;(function () {var reactHotLoader = __webpack_require__(0).default;var leaveModule = __webpack_require__(0).leaveModule;if (!reactHotLoader) {return;}reactHotLoader.register(simplerReduxReducerKey, 'simplerReduxReducerKey', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(simplerReduxObjectToMergeKey, 'simplerReduxObjectToMergeKey', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(objectType, 'objectType', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(isObjectType, 'isObjectType', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(srOptions, 'srOptions', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(makeSetRState, 'makeSetRState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(makeGetRState, 'makeGetRState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(addListener, 'addListener', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(generalReducer, 'generalReducer', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(buildAddReducer, 'buildAddReducer', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(registerSimplerRedux, 'registerSimplerRedux', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(oneReducer, 'oneReducer', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(createStore, 'createStore', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(getStateFunction, 'getStateFunction', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(setStateFunction, 'setStateFunction', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(makeSharedModuleKeyName, 'makeSharedModuleKeyName', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(reducersPreloadedState, 'reducersPreloadedState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(getState, 'getState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(setState, 'setState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(stateAccessors, 'stateAccessors', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');reactHotLoader.register(createModuleData, 'createModuleData', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/simpler-redux.js');leaveModule(module);})();; 417 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)(module), __webpack_require__(3))) 418 | 419 | /***/ }), 420 | /* 3 */ 421 | /***/ (function(module, exports) { 422 | 423 | // shim for using process in browser 424 | var process = module.exports = {}; 425 | 426 | // cached from whatever global is present so that test runners that stub it 427 | // don't break things. But we need to wrap it in a try catch in case it is 428 | // wrapped in strict mode code which doesn't define any globals. It's inside a 429 | // function because try/catches deoptimize in certain engines. 430 | 431 | var cachedSetTimeout; 432 | var cachedClearTimeout; 433 | 434 | function defaultSetTimout() { 435 | throw new Error('setTimeout has not been defined'); 436 | } 437 | function defaultClearTimeout () { 438 | throw new Error('clearTimeout has not been defined'); 439 | } 440 | (function () { 441 | try { 442 | if (typeof setTimeout === 'function') { 443 | cachedSetTimeout = setTimeout; 444 | } else { 445 | cachedSetTimeout = defaultSetTimout; 446 | } 447 | } catch (e) { 448 | cachedSetTimeout = defaultSetTimout; 449 | } 450 | try { 451 | if (typeof clearTimeout === 'function') { 452 | cachedClearTimeout = clearTimeout; 453 | } else { 454 | cachedClearTimeout = defaultClearTimeout; 455 | } 456 | } catch (e) { 457 | cachedClearTimeout = defaultClearTimeout; 458 | } 459 | } ()) 460 | function runTimeout(fun) { 461 | if (cachedSetTimeout === setTimeout) { 462 | //normal enviroments in sane situations 463 | return setTimeout(fun, 0); 464 | } 465 | // if setTimeout wasn't available but was latter defined 466 | if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { 467 | cachedSetTimeout = setTimeout; 468 | return setTimeout(fun, 0); 469 | } 470 | try { 471 | // when when somebody has screwed with setTimeout but no I.E. maddness 472 | return cachedSetTimeout(fun, 0); 473 | } catch(e){ 474 | try { 475 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 476 | return cachedSetTimeout.call(null, fun, 0); 477 | } catch(e){ 478 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error 479 | return cachedSetTimeout.call(this, fun, 0); 480 | } 481 | } 482 | 483 | 484 | } 485 | function runClearTimeout(marker) { 486 | if (cachedClearTimeout === clearTimeout) { 487 | //normal enviroments in sane situations 488 | return clearTimeout(marker); 489 | } 490 | // if clearTimeout wasn't available but was latter defined 491 | if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { 492 | cachedClearTimeout = clearTimeout; 493 | return clearTimeout(marker); 494 | } 495 | try { 496 | // when when somebody has screwed with setTimeout but no I.E. maddness 497 | return cachedClearTimeout(marker); 498 | } catch (e){ 499 | try { 500 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 501 | return cachedClearTimeout.call(null, marker); 502 | } catch (e){ 503 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. 504 | // Some versions of I.E. have different rules for clearTimeout vs setTimeout 505 | return cachedClearTimeout.call(this, marker); 506 | } 507 | } 508 | 509 | 510 | 511 | } 512 | var queue = []; 513 | var draining = false; 514 | var currentQueue; 515 | var queueIndex = -1; 516 | 517 | function cleanUpNextTick() { 518 | if (!draining || !currentQueue) { 519 | return; 520 | } 521 | draining = false; 522 | if (currentQueue.length) { 523 | queue = currentQueue.concat(queue); 524 | } else { 525 | queueIndex = -1; 526 | } 527 | if (queue.length) { 528 | drainQueue(); 529 | } 530 | } 531 | 532 | function drainQueue() { 533 | if (draining) { 534 | return; 535 | } 536 | var timeout = runTimeout(cleanUpNextTick); 537 | draining = true; 538 | 539 | var len = queue.length; 540 | while(len) { 541 | currentQueue = queue; 542 | queue = []; 543 | while (++queueIndex < len) { 544 | if (currentQueue) { 545 | currentQueue[queueIndex].run(); 546 | } 547 | } 548 | queueIndex = -1; 549 | len = queue.length; 550 | } 551 | currentQueue = null; 552 | draining = false; 553 | runClearTimeout(timeout); 554 | } 555 | 556 | process.nextTick = function (fun) { 557 | var args = new Array(arguments.length - 1); 558 | if (arguments.length > 1) { 559 | for (var i = 1; i < arguments.length; i++) { 560 | args[i - 1] = arguments[i]; 561 | } 562 | } 563 | queue.push(new Item(fun, args)); 564 | if (queue.length === 1 && !draining) { 565 | runTimeout(drainQueue); 566 | } 567 | }; 568 | 569 | // v8 likes predictible objects 570 | function Item(fun, array) { 571 | this.fun = fun; 572 | this.array = array; 573 | } 574 | Item.prototype.run = function () { 575 | this.fun.apply(null, this.array); 576 | }; 577 | process.title = 'browser'; 578 | process.browser = true; 579 | process.env = {}; 580 | process.argv = []; 581 | process.version = ''; // empty string to avoid regexp issues 582 | process.versions = {}; 583 | 584 | function noop() {} 585 | 586 | process.on = noop; 587 | process.addListener = noop; 588 | process.once = noop; 589 | process.off = noop; 590 | process.removeListener = noop; 591 | process.removeAllListeners = noop; 592 | process.emit = noop; 593 | process.prependListener = noop; 594 | process.prependOnceListener = noop; 595 | 596 | process.listeners = function (name) { return [] } 597 | 598 | process.binding = function (name) { 599 | throw new Error('process.binding is not supported'); 600 | }; 601 | 602 | process.cwd = function () { return '/' }; 603 | process.chdir = function (dir) { 604 | throw new Error('process.chdir is not supported'); 605 | }; 606 | process.umask = function() { return 0; }; 607 | 608 | 609 | /***/ }), 610 | /* 4 */ 611 | /***/ (function(module, exports, __webpack_require__) { 612 | 613 | "use strict"; 614 | /* WEBPACK VAR INJECTION */(function(module) {Object.defineProperty(exports, "__esModule", { value: true });var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};(function () {var enterModule = __webpack_require__(0).enterModule;enterModule && enterModule(module);})();var totalMSTPCalcs = 0; 615 | var totalMSTPCache = 0; 616 | 617 | var shallowSubObjectCompare = exports.shallowSubObjectCompare = function shallowSubObjectCompare(obj, subObj, subObjectkeys) { 618 | var len = subObjectkeys.length; 619 | for (var i = 0; i < len; ++i) { 620 | var key = subObjectkeys[i]; 621 | if (subObj[key] !== obj[key]) { 622 | return false; 623 | } 624 | } 625 | return true; 626 | }; 627 | 628 | var shallowCopy = exports.shallowCopy = function shallowCopy(obj, copykeys) { 629 | if (copykeys === undefined) { 630 | copykeys = Object.keys(obj); 631 | } 632 | var subObj = {}; 633 | var len = copykeys.length; 634 | for (var i = 0; i < len; ++i) { 635 | var key = copykeys[i]; 636 | subObj[key] = obj[key]; 637 | } 638 | return subObj; 639 | }; 640 | 641 | var displayMessage = function displayMessage(obj) { 642 | if (!obj.prevState) { 643 | console.log(obj.msg); 644 | } else { 645 | console.log(obj.msg + "\nprevState: %onextState: %o", 646 | 647 | obj.prevState, 648 | obj.nextState); 649 | 650 | } 651 | 652 | // Give the stats as performance feedback to developers. 653 | console.log("totalMSTPCalcs=" + totalMSTPCalcs + ", totalMSTPCache=" + totalMSTPCache); 654 | }; 655 | 656 | var mapStateToPropsCache = function mapStateToPropsCache(obj) { 657 | totalMSTPCache++; 658 | displayMessage(obj); 659 | }; 660 | 661 | var mapStateToPropsCalc = function mapStateToPropsCalc(obj) { 662 | totalMSTPCalcs++; 663 | displayMessage(obj); 664 | }; 665 | 666 | var defaultOptions = exports.defaultOptions = function defaultOptions(options) { 667 | if (options === undefined) { 668 | return; 669 | } 670 | options = _extends({}, options); 671 | if (!options.mapStateToPropsCache) { 672 | options.mapStateToPropsCache = function () {}; 673 | } 674 | if (!options.mapStateToPropsCalc) { 675 | options.mapStateToPropsCalc = function () {}; 676 | } 677 | if (options.useDefaultMSTPCacheLogging) { 678 | options.mapStateToPropsCalc = mapStateToPropsCalc; 679 | options.mapStateToPropsCache = mapStateToPropsCache; 680 | } 681 | return options; 682 | };;(function () {var reactHotLoader = __webpack_require__(0).default;var leaveModule = __webpack_require__(0).leaveModule;if (!reactHotLoader) {return;}reactHotLoader.register(totalMSTPCalcs, "totalMSTPCalcs", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(totalMSTPCache, "totalMSTPCache", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(shallowSubObjectCompare, "shallowSubObjectCompare", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(shallowCopy, "shallowCopy", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(displayMessage, "displayMessage", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(mapStateToPropsCache, "mapStateToPropsCache", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(mapStateToPropsCalc, "mapStateToPropsCalc", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");reactHotLoader.register(defaultOptions, "defaultOptions", "C:/Users/Andrew/Documents/GitHub/simpler-redux/src/util.js");leaveModule(module);})();; 683 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)(module))) 684 | 685 | /***/ }), 686 | /* 5 */ 687 | /***/ (function(module, exports) { 688 | 689 | module.exports = require("react"); 690 | 691 | /***/ }), 692 | /* 6 */ 693 | /***/ (function(module, exports, __webpack_require__) { 694 | 695 | "use strict"; 696 | Object.defineProperty(exports, "__esModule", { value: true });var _simplerRedux = __webpack_require__(2);Object.keys(_simplerRedux).forEach(function (key) {if (key === "default" || key === "__esModule") return;Object.defineProperty(exports, key, { enumerable: true, get: function get() {return _simplerRedux[key];} });});var _reactSimplerRedux = __webpack_require__(10); 697 | Object.keys(_reactSimplerRedux).forEach(function (key) {if (key === "default" || key === "__esModule") return;Object.defineProperty(exports, key, { enumerable: true, get: function get() {return _reactSimplerRedux[key];} });}); 698 | 699 | /***/ }), 700 | /* 7 */ 701 | /***/ (function(module, exports) { 702 | 703 | module.exports = require("redux"); 704 | 705 | /***/ }), 706 | /* 8 */ 707 | /***/ (function(module, exports, __webpack_require__) { 708 | 709 | "use strict"; 710 | function _interopDefault(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var React=_interopDefault(__webpack_require__(5)),classCallCheck=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},inherits=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)},possibleConstructorReturn=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t},AppContainer=function(e){function t(){return classCallCheck(this,t),possibleConstructorReturn(this,e.apply(this,arguments))}return inherits(t,e),t.prototype.render=function(){return React.Children.only(this.props.children)},t}(React.Component),hot_prod=function(){return function(e){return e}},areComponentsEqual=function(e,t){return e===t},setConfig=function(){};exports.AppContainer=AppContainer,exports.hot=hot_prod,exports.areComponentsEqual=areComponentsEqual,exports.setConfig=setConfig; 711 | 712 | 713 | /***/ }), 714 | /* 9 */ 715 | /***/ (function(module, exports, __webpack_require__) { 716 | 717 | "use strict"; 718 | /* WEBPACK VAR INJECTION */(function(module) {Object.defineProperty(exports, "__esModule", { value: true });(function () {var enterModule = __webpack_require__(0).enterModule;enterModule && enterModule(module);})();function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}var proxyDefined = function proxyDefined() {return ( 719 | typeof Proxy !== 'undefined');}; 720 | 721 | var getReducerKeyValue = function getReducerKeyValue(simplerReduxStore, reducerKey, key) {return ( 722 | simplerReduxStore.getRState(reducerKey)[key]);}; 723 | 724 | var setReducerKeyValue = function setReducerKeyValue(simplerReduxStore, reducerKey, key, value) { 725 | simplerReduxStore.setRState(reducerKey, _defineProperty({}, key, value)); 726 | return true; 727 | }; 728 | 729 | var defineProxyGetSet = function defineProxyGetSet(obj, simplerReduxStore, reducerKey, key) { 730 | Object.defineProperty( 731 | obj, 732 | key, { 733 | get: function get() {return ( 734 | getReducerKeyValue(simplerReduxStore, reducerKey, key));}, 735 | set: function set(value) {return ( 736 | setReducerKeyValue(simplerReduxStore, reducerKey, key, value));} }); 737 | 738 | 739 | }; 740 | 741 | var simulateProxy = function simulateProxy(simplerReduxStore, reducerKey, initialState) {return ( 742 | Object.keys(initialState).reduce(function (obj, key) { 743 | defineProxyGetSet(obj, simplerReduxStore, reducerKey, key); 744 | return obj; 745 | }, {}));}; 746 | 747 | var getProxyHandler = function getProxyHandler(reducerKey) { 748 | return { 749 | get: function get(simplerReduxStore, key) {return ( 750 | getReducerKeyValue(simplerReduxStore, reducerKey, key));}, 751 | set: function set(simplerReduxStore, key, value) {return ( 752 | setReducerKeyValue(simplerReduxStore, reducerKey, key, value));} }; 753 | 754 | };var _default = 755 | 756 | function _default(simplerReduxStore, reducerKey, initialState) { 757 | if (proxyDefined()) { 758 | return new Proxy(simplerReduxStore, getProxyHandler(reducerKey)); 759 | } 760 | return simulateProxy(simplerReduxStore, reducerKey, initialState); 761 | };exports.default = _default;;(function () {var reactHotLoader = __webpack_require__(0).default;var leaveModule = __webpack_require__(0).leaveModule;if (!reactHotLoader) {return;}reactHotLoader.register(proxyDefined, 'proxyDefined', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');reactHotLoader.register(getReducerKeyValue, 'getReducerKeyValue', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');reactHotLoader.register(setReducerKeyValue, 'setReducerKeyValue', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');reactHotLoader.register(defineProxyGetSet, 'defineProxyGetSet', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');reactHotLoader.register(simulateProxy, 'simulateProxy', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');reactHotLoader.register(getProxyHandler, 'getProxyHandler', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');reactHotLoader.register(_default, 'default', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/proxy.js');leaveModule(module);})();; 762 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)(module))) 763 | 764 | /***/ }), 765 | /* 10 */ 766 | /***/ (function(module, exports, __webpack_require__) { 767 | 768 | "use strict"; 769 | /* WEBPACK VAR INJECTION */(function(module, process) {Object.defineProperty(exports, "__esModule", { value: true });exports.connectWithStore = exports.connectLifeCycleComponentWithStore = exports.hookedLifeCycleComponent = exports.buildSelectorsFromUIState = exports.allStateToPropsUsingSelectorList = exports.allStateToPropsUsingSelectors = exports.allStateToProps = exports.allServiceFunctionsToPropsUsingServiceFunctionList = exports.allServiceFunctionsToProps = exports.allServiceFunctionsToPropsWithStore = undefined;var _jsxFileName = 'C:\\Users\\Andrew\\Documents\\GitHub\\simpler-redux\\src\\react-simpler-redux.js';var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _react = __webpack_require__(5);var _react2 = _interopRequireDefault(_react); 770 | var _reactRedux = __webpack_require__(11); 771 | var _propTypes = __webpack_require__(12);var _propTypes2 = _interopRequireDefault(_propTypes); 772 | var _hoistNonReactStatics = __webpack_require__(13);var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics); 773 | var _simplerRedux = __webpack_require__(2); 774 | var _util = __webpack_require__(4);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}(function () {var enterModule = __webpack_require__(0).enterModule;enterModule && enterModule(module);})();function _objectWithoutProperties(obj, keys) {var target = {};for (var i in obj) {if (keys.indexOf(i) >= 0) continue;if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;target[i] = obj[i];}return target;}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self, call) {if (!self) {throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call && (typeof call === "object" || typeof call === "function") ? call : self;}function _inherits(subClass, superClass) {if (typeof superClass !== "function" && superClass !== null) {throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}function _toConsumableArray(arr) {if (Array.isArray(arr)) {for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {arr2[i] = arr[i];}return arr2;} else {return Array.from(arr);}} 775 | 776 | // React lifecycle events supported in the model code. 777 | var reactLifeCycleEvents = { 778 | onConstructor: 'onConstructor', 779 | onRender: 'onRender', 780 | componentDidMount: 'componentDidMount', 781 | componentWillUnmount: 'componentWillUnmount', 782 | componentDidCatch: 'componentDidCatch', 783 | componentToRender: 'componentToRender' 784 | 785 | 786 | // 787 | // Builds a mapDispatchToProps function based on the service functions and adds the store as 788 | // the first parameter to each service function and the rest of the parameters given 789 | // by the react UI component when called in react. 790 | // 791 | };var allServiceFunctionsToPropsWithStore = exports.allServiceFunctionsToPropsWithStore = function allServiceFunctionsToPropsWithStore(serviceFunctions) { 792 | var keys = Object.keys(serviceFunctions); 793 | return function (_dispatch, ownProps) {return ( 794 | keys.reduce(function (obj, e) { 795 | obj[e] = function () {for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {args[_key] = arguments[_key];}return serviceFunctions[e].apply(serviceFunctions, [ownProps.store].concat(args));}; 796 | return obj; 797 | }, {}));}; 798 | }; 799 | 800 | // 801 | // Builds a mapDispatchToProps function based on the service functions and adds 802 | // all of the parameters given by the react UI component when called in react. 803 | // 804 | var allServiceFunctionsToProps = exports.allServiceFunctionsToProps = function allServiceFunctionsToProps(serviceFunctions) {return ( 805 | function () {return _extends({}, serviceFunctions);});}; 806 | 807 | // 808 | // Builds a mapDispatchToProps function based on a serviceFunctionList object. 809 | // This allows including service functions from various modules. 810 | // The keylist allows selecting only a subset of a module's service functions. 811 | // If keylist is not specified then all service functions will be included. 812 | // 813 | var allServiceFunctionsToPropsUsingServiceFunctionList = exports.allServiceFunctionsToPropsUsingServiceFunctionList = function allServiceFunctionsToPropsUsingServiceFunctionList(serviceFunctionList) { 814 | serviceFunctionList = [].concat(_toConsumableArray(serviceFunctionList)); 815 | return function (_dispatch, ownProps) {return ( 816 | serviceFunctionList.reduce(function (obj, e) { 817 | var keylist = e.keylist ? e.keylist : Object.keys(e.serviceFunctions); 818 | keylist.forEach(function (key) { 819 | if (e.withStore) { 820 | obj[key] = function () {var _e$serviceFunctions;for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {args[_key2] = arguments[_key2];}return (_e$serviceFunctions = e.serviceFunctions)[key].apply(_e$serviceFunctions, [ownProps.store].concat(args));}; 821 | } else { 822 | obj[key] = function () {var _e$serviceFunctions2;return (_e$serviceFunctions2 = e.serviceFunctions)[key].apply(_e$serviceFunctions2, arguments);}; 823 | } 824 | }); 825 | return obj; 826 | }, {}));}; 827 | }; 828 | 829 | // 830 | // Builds a mapStateToProps function that returns the entire reducer state. 831 | // 832 | var allStateToProps = exports.allStateToProps = function allStateToProps(reducerKey) { 833 | return function (state) { 834 | if (process.env.NODE_ENV !== 'production') { 835 | _simplerRedux.srOptions.mapStateToPropsCache({ 836 | msg: 'mapStateToProps fast calculation at ' + reducerKey + ', cache not needed.', 837 | reducerKey: reducerKey }); 838 | 839 | } 840 | return state[reducerKey]; 841 | }; 842 | }; 843 | 844 | // 845 | // Builds a mapStateToProps function based on a selectors object. 846 | // 847 | var allStateToPropsUsingSelectors = exports.allStateToPropsUsingSelectors = function allStateToPropsUsingSelectors(selectors, reducerKey) { 848 | var keys = Object.keys(selectors); 849 | if (!reducerKey) { 850 | return ( 851 | function (state) {return ( 852 | keys.reduce(function (obj, e) { 853 | obj[e] = selectors[e](state); 854 | return obj; 855 | }, {}));}); 856 | 857 | } 858 | // Implements a caching mechanism below such that if the state at the reducerKey did not change 859 | // then we return the previously calculated object. 860 | var prevState = void 0; 861 | var lastresult = void 0; 862 | return function (state) { 863 | if (prevState !== state[reducerKey]) { 864 | if (process.env.NODE_ENV !== 'production') { 865 | _simplerRedux.srOptions.mapStateToPropsCalc({ 866 | msg: 'selector calculated ' + reducerKey, 867 | prevState: prevState, 868 | nextState: state[reducerKey], 869 | reducerKey: reducerKey }); 870 | 871 | } 872 | prevState = state[reducerKey]; 873 | lastresult = keys.reduce(function (obj, e) { 874 | obj[e] = selectors[e](state); 875 | return obj; 876 | }, {}); 877 | } else { 878 | if (process.env.NODE_ENV !== 'production') { 879 | _simplerRedux.srOptions.mapStateToPropsCache({ 880 | msg: 'selector cache ' + reducerKey, 881 | prevState: prevState, 882 | nextState: state[reducerKey], 883 | reducerKey: reducerKey }); 884 | 885 | } 886 | } 887 | return lastresult; 888 | }; 889 | }; 890 | 891 | // 892 | // Builds a mapStateToProps function based on a selectorList object. 893 | // This allows including selectors from various modules. 894 | // The keylist allows selecting only a subset of a module's selectors. 895 | // If keylist is not specified then all selectors will be included. 896 | // 897 | var allStateToPropsUsingSelectorList = exports.allStateToPropsUsingSelectorList = function allStateToPropsUsingSelectorList(selectorList, componentName) { 898 | selectorList = [].concat(_toConsumableArray(selectorList)); 899 | var useCache = true; 900 | var reducerKeys = []; 901 | for (var i = 0; i < selectorList.length; ++i) { 902 | if (selectorList[i].keylist === undefined) { 903 | selectorList[i].keylist = Object.keys(selectorList[i].selectors); 904 | } 905 | var reducerKey = selectorList[i].reducerKey; 906 | if (reducerKey) { 907 | reducerKeys.push(reducerKey); 908 | } else { 909 | useCache = false; 910 | } 911 | } 912 | if (componentName === undefined) { 913 | componentName = reducerKeys.toString(); 914 | } 915 | if (useCache) { 916 | var prevStates = {}; 917 | var lastResult = void 0; 918 | return function (state) { 919 | if (!(0, _util.shallowSubObjectCompare)(state, prevStates, reducerKeys)) { 920 | if (process.env.NODE_ENV !== 'production') { 921 | _simplerRedux.srOptions.mapStateToPropsCalc({ 922 | msg: 'Selector List calculated ' + componentName + '.', 923 | nextState: (0, _util.shallowCopy)(state, reducerKeys), 924 | prevState: prevStates, 925 | reducerKeys: reducerKeys }); 926 | 927 | } 928 | prevStates = (0, _util.shallowCopy)(state, reducerKeys); 929 | lastResult = selectorList.reduce(function (obj, e) { 930 | e.keylist.forEach(function (key) { 931 | obj[key] = e.selectors[key](state); 932 | }); 933 | return obj; 934 | }, {}); 935 | } else { 936 | if (process.env.NODE_ENV !== 'production') { 937 | _simplerRedux.srOptions.mapStateToPropsCache({ 938 | msg: 'Selector List cache ' + componentName + '.', 939 | nextState: (0, _util.shallowCopy)(state, reducerKeys), 940 | prevState: prevStates, 941 | reducerKeys: reducerKeys }); 942 | 943 | } 944 | } 945 | return lastResult; 946 | }; 947 | } 948 | reducerKeys = []; 949 | return function (state) {return ( 950 | selectorList.reduce(function (obj, e) { 951 | e.keylist.forEach(function (key) { 952 | obj[key] = e.selectors[key](state); 953 | }); 954 | return obj; 955 | }, {}));}; 956 | }; 957 | 958 | // 959 | // Builds a model selectors object from either initialUIState or initialState. 960 | // initialUIState should only contain keys that you want in the 961 | // props of the react component. 962 | // This is not for specialized selectors for the UI that require conjunctions or 963 | // selectors from other modules, etc. 964 | // It is only for simple selectors of the nature state => state[reducerKey][stateKey] 965 | // 966 | var buildSelectorsFromUIState = exports.buildSelectorsFromUIState = function buildSelectorsFromUIState(reducerKey, initialState) { 967 | var keys = Object.keys(initialState); 968 | return keys.reduce(function (obj, e) { 969 | obj[e] = function (state) {return state[reducerKey][e];}; 970 | return obj; 971 | }, {}); 972 | }; 973 | 974 | // 975 | // Builds a mapStateToProps function that returns key/UI State pairs 976 | // 977 | var allStateToPropsUsingUIState = function allStateToPropsUsingUIState(reducerKey, initialUIState) { 978 | var keys = Object.keys(initialUIState); 979 | var prevState = void 0; 980 | var lastResult = void 0; 981 | return function (state) { 982 | if (state[reducerKey] !== prevState) { 983 | if (process.env.NODE_ENV !== 'production') { 984 | _simplerRedux.srOptions.mapStateToPropsCalc({ 985 | msg: 'State props calculated at ' + reducerKey + '.', 986 | nextState: state[reducerKey], 987 | prevState: prevState, 988 | reducerKey: reducerKey }); 989 | 990 | } 991 | prevState = state[reducerKey]; 992 | lastResult = (0, _util.shallowCopy)(state[reducerKey], keys); 993 | } else { 994 | if (process.env.NODE_ENV !== 'production') { 995 | _simplerRedux.srOptions.mapStateToPropsCache({ 996 | msg: 'State props cache at ' + reducerKey + '.', 997 | nextState: state[reducerKey], 998 | prevState: prevState, 999 | reducerKey: reducerKey }); 1000 | 1001 | } 1002 | } 1003 | return lastResult; 1004 | }; 1005 | }; 1006 | 1007 | var buildCachedMapStateToProps = function buildCachedMapStateToProps(mapStateToProps, mapStateToPropsReducerKeys, componentName) { 1008 | if (!Array.isArray(mapStateToPropsReducerKeys)) { 1009 | mapStateToPropsReducerKeys = [mapStateToPropsReducerKeys]; 1010 | } 1011 | if (componentName === undefined) { 1012 | componentName = mapStateToPropsReducerKeys.toString(); 1013 | } 1014 | var prevStates = {}; 1015 | var lastResult = void 0; 1016 | return function (state) { 1017 | if (!(0, _util.shallowSubObjectCompare)(state, prevStates, mapStateToPropsReducerKeys)) { 1018 | if (process.env.NODE_ENV !== 'production') { 1019 | _simplerRedux.srOptions.mapStateToPropsCalc({ 1020 | msg: 'mapStateToProps calculated ' + componentName + '.', 1021 | nextState: (0, _util.shallowCopy)(state, mapStateToPropsReducerKeys), 1022 | prevState: prevStates, 1023 | reducerKeys: mapStateToPropsReducerKeys }); 1024 | 1025 | } 1026 | prevStates = (0, _util.shallowCopy)(state, mapStateToPropsReducerKeys); 1027 | lastResult = mapStateToProps(state); 1028 | } else { 1029 | if (process.env.NODE_ENV !== 'production') { 1030 | _simplerRedux.srOptions.mapStateToPropsCache({ 1031 | msg: 'mapStateToProps cache ' + componentName + '.', 1032 | nextState: (0, _util.shallowCopy)(state, mapStateToPropsReducerKeys), 1033 | prevState: prevStates, 1034 | reducerKeys: mapStateToPropsReducerKeys }); 1035 | 1036 | } 1037 | } 1038 | return lastResult; 1039 | }; 1040 | }; 1041 | 1042 | var connectWithStoreBase = function connectWithStoreBase( 1043 | options) 1044 | {var 1045 | 1046 | uiComponent = 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | options.uiComponent,reduxOptions = options.reduxOptions,storeIsDefinedCallback = options.storeIsDefinedCallback,reducerKey = options.reducerKey,isDynamicReducer = options.isDynamicReducer,initialState = options.initialState,mapStateToProps = options.mapStateToProps,mapDispatchToProps = options.mapDispatchToProps,serviceFunctions = options.serviceFunctions,serviceFunctionList = options.serviceFunctionList,selectors = options.selectors,selectorList = options.selectorList,initialUIState = options.initialUIState,noStoreParameterOnServiceFunctions = options.noStoreParameterOnServiceFunctions,mergeProps = options.mergeProps,mapStateToPropsReducerKeys = options.mapStateToPropsReducerKeys,componentName = options.componentName; 1064 | 1065 | if (process.env.NODE_ENV !== 'production') { 1066 | if (uiComponent === undefined) { 1067 | throw new Error('connectWithStore: options.uiComponent cannot be undefined, reducerKey=' + reducerKey + '.'); 1068 | } 1069 | if (storeIsDefinedCallback && typeof storeIsDefinedCallback !== 'function') { 1070 | throw new Error('connectWithStore: options.storeIsDefinedCallback must be a function, reducerKey=' + reducerKey + '.'); 1071 | } 1072 | if (selectors !== undefined && initialUIState !== undefined) { 1073 | throw new Error('connectWithStore: Cannot export both selectors and initialUIState, reducerKey=' + reducerKey + '.'); 1074 | } 1075 | var displayName = ''; 1076 | if (uiComponent !== undefined) { 1077 | if (uiComponent.displayName !== undefined) { 1078 | displayName = uiComponent.displayName; 1079 | } else { 1080 | displayName = uiComponent.name; 1081 | } 1082 | } 1083 | if (selectorList !== undefined) { 1084 | selectorList.forEach(function (e) { 1085 | if (e.keylist !== undefined) { 1086 | e.keylist.forEach(function (key) { 1087 | if (typeof e.selectors[key] !== 'function') { 1088 | throw new Error('connectWithStore ' + displayName + ': The selectors key ' + key + ' is not in the selectors ' + e.keylist.toString() + '.'); 1089 | } 1090 | }); 1091 | } 1092 | }); 1093 | } 1094 | if (serviceFunctionList !== undefined) { 1095 | serviceFunctionList.forEach(function (e) { 1096 | if (e.keylist !== undefined) { 1097 | e.keylist.forEach(function (key) { 1098 | if (typeof e.serviceFunctions[key] !== 'function') { 1099 | throw new Error('connectWithStore ' + displayName + ': The serviceFunctions key ' + key + ' is not in the serviceFunctionList ' + e.keylist.toString() + '.'); 1100 | } 1101 | }); 1102 | } 1103 | }); 1104 | } 1105 | } 1106 | 1107 | if (componentName === undefined) { 1108 | componentName = reducerKey; 1109 | } 1110 | 1111 | // Default initialState (reducer state) to initialUIState (component props state). 1112 | if (initialState === undefined) { 1113 | initialState = initialUIState; 1114 | } 1115 | 1116 | // Default initialUIState (component props state) to initialState (reducer state). 1117 | if (initialUIState === undefined) { 1118 | initialUIState = initialState; 1119 | } 1120 | 1121 | var withRef = reduxOptions && reduxOptions.withRef; 1122 | if (initialState !== undefined) { 1123 | initialState = _extends({}, initialState); 1124 | } 1125 | 1126 | // If mapStateToProps is defined by the consumer then keep it no matter what. 1127 | if (mapStateToProps !== undefined) { 1128 | if (mapStateToPropsReducerKeys !== undefined) { 1129 | mapStateToProps = buildCachedMapStateToProps(mapStateToProps, mapStateToPropsReducerKeys, componentName); 1130 | } 1131 | } else { 1132 | if (selectorList !== undefined) { 1133 | mapStateToProps = allStateToPropsUsingSelectorList(selectorList, componentName); 1134 | } else if (selectors !== undefined) { 1135 | mapStateToProps = allStateToPropsUsingSelectors(selectors, reducerKey); 1136 | } else if (reducerKey !== undefined && initialUIState !== undefined) { 1137 | // This is for efficiency. initialUIState and initialState are the same so 1138 | // mapStateToProps simply returns the entire reducerKey state. 1139 | if (Object.keys(initialUIState).length === Object.keys(initialState).length) { 1140 | mapStateToProps = allStateToProps(reducerKey); 1141 | } else { 1142 | mapStateToProps = allStateToPropsUsingUIState(reducerKey, initialUIState); 1143 | } 1144 | } 1145 | } 1146 | 1147 | // If mapDispatchToProps is defined by the consumer then keep it no matter what. 1148 | if (mapDispatchToProps === undefined) { 1149 | if (serviceFunctionList !== undefined) { 1150 | mapDispatchToProps = allServiceFunctionsToPropsUsingServiceFunctionList(serviceFunctionList); 1151 | } else if (serviceFunctions !== undefined) { 1152 | if (noStoreParameterOnServiceFunctions) { 1153 | mapDispatchToProps = allServiceFunctionsToProps(serviceFunctions); 1154 | } else { 1155 | mapDispatchToProps = allServiceFunctionsToPropsWithStore(serviceFunctions); 1156 | } 1157 | } 1158 | } 1159 | 1160 | // Call the react-redux connect. 1161 | var ConnectedComponent = (0, _reactRedux.connect)( 1162 | mapStateToProps, 1163 | mapDispatchToProps, 1164 | mergeProps, 1165 | reduxOptions)( 1166 | uiComponent);var 1167 | 1168 | HOC = function (_React$Component) {_inherits(HOC, _React$Component); 1169 | function HOC(props, context) {_classCallCheck(this, HOC); 1170 | 1171 | // Handles the dynamic loading of the reducer. 1172 | var _this = _possibleConstructorReturn(this, (HOC.__proto__ || Object.getPrototypeOf(HOC)).call(this, props, context));if (isDynamicReducer !== false && _this.context.store.isDynamicReducerLoading()) { 1173 | // This will build the reducer and add it to the reducers object. 1174 | if (reducerKey !== undefined && initialState !== undefined) { 1175 | _this.context.store.addReducer(reducerKey, initialState); 1176 | } 1177 | } 1178 | // Handles a callback for the consumer to cache and/or use the store. 1179 | if (storeIsDefinedCallback) { 1180 | storeIsDefinedCallback(_this.context.store, _simplerRedux.stateAccessors); 1181 | } 1182 | _this.setWrappedInstance = _this.setWrappedInstance.bind(_this);return _this; 1183 | } 1184 | // Support the redux connect getWrappedInstance out of this HOC. 1185 | // This way, the consumer does nothing different when using this HOC 1186 | // vs redux connected components when handling refs. 1187 | _createClass(HOC, [{ key: 'setWrappedInstance', value: function setWrappedInstance(ref) { 1188 | if (!withRef) { 1189 | return; 1190 | } 1191 | // Refer to the original instance of the component wrapped with connect. 1192 | if (ref) { 1193 | if (process.env.NODE_ENV !== 'production') { 1194 | if (typeof ref.getWrappedInstance !== 'function') { 1195 | console.log('There is something wrong with redux connect.'); 1196 | return; 1197 | } 1198 | } 1199 | this.wrappedInstance = ref.getWrappedInstance(); 1200 | } 1201 | } }, { key: 'getWrappedInstance', value: function getWrappedInstance() 1202 | { 1203 | if (process.env.NODE_ENV !== 'production') { 1204 | if (this.wrappedInstance === undefined) { 1205 | console.log('The getWrappedInstance return is undefined. Did you use the withRef: true option?'); 1206 | } 1207 | } 1208 | return this.wrappedInstance; 1209 | } }, { key: 'render', value: function render() 1210 | { 1211 | // Add the store to the props of the redux connected component so that it can be referenced 1212 | // in mapDispatchToProps with ownProps. 1213 | return ( 1214 | _react2.default.createElement(ConnectedComponent, _extends({}, 1215 | this.props, { 1216 | ref: this.setWrappedInstance, 1217 | store: this.context.store, __source: { fileName: _jsxFileName, lineNumber: 446 } }))); 1218 | 1219 | 1220 | } }, { key: '__reactstandin__regenerateByEval', // @ts-ignore 1221 | value: function __reactstandin__regenerateByEval(key, code) {// @ts-ignore 1222 | this[key] = eval(code);} }]);return HOC;}(_react2.default.Component); 1223 | HOC.displayName = 'connectWithStore(' + (ConnectedComponent.displayName || ConnectedComponent.name) + ')'; 1224 | // Opt in for the context. 1225 | HOC.contextTypes = { 1226 | store: _propTypes2.default.object }; 1227 | 1228 | return (0, _hoistNonReactStatics2.default)(HOC, ConnectedComponent); 1229 | }; 1230 | 1231 | // 1232 | // This supports moving the react life cycle events into the model/business code serviceFunctions functions object. 1233 | // 1234 | var ReactLifeCycleComponent = function (_React$Component2) {_inherits(ReactLifeCycleComponent, _React$Component2); 1235 | function ReactLifeCycleComponent(props) {_classCallCheck(this, ReactLifeCycleComponent);var _this2 = _possibleConstructorReturn(this, (ReactLifeCycleComponent.__proto__ || Object.getPrototypeOf(ReactLifeCycleComponent)).call(this, 1236 | props)); 1237 | _this2.runFunction = _this2.runFunction.bind(_this2); 1238 | _this2.runFunction(_this2.props.onConstructor);return _this2; 1239 | }_createClass(ReactLifeCycleComponent, [{ key: 'runFunction', value: function runFunction( 1240 | func) {var args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 1241 | return func ? func.call.apply(func, [this].concat(_toConsumableArray(args))) : null; 1242 | } }, { key: 'componentDidMount', value: function componentDidMount() 1243 | { 1244 | this.runFunction(this.props.componentDidMount); 1245 | } }, { key: 'componentWillUnmount', value: function componentWillUnmount() 1246 | { 1247 | this.runFunction(this.props.componentWillUnmount); 1248 | } }, { key: 'componentDidCatch', value: function componentDidCatch() 1249 | {for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {args[_key3] = arguments[_key3];} 1250 | this.runFunction(this.props.componentDidCatch, args); 1251 | } }, { key: 'render', value: function render() 1252 | { 1253 | this.runFunction(this.props.onRender); 1254 | // Render prop 1255 | return this.props.componentToRender(); 1256 | } }, { key: '__reactstandin__regenerateByEval', // @ts-ignore 1257 | value: function __reactstandin__regenerateByEval(key, code) {// @ts-ignore 1258 | this[key] = eval(code);} }]);return ReactLifeCycleComponent;}(_react2.default.Component); 1259 | ReactLifeCycleComponent.propTypes = { 1260 | onConstructor: _propTypes2.default.func, 1261 | onRender: _propTypes2.default.func, 1262 | componentDidMount: _propTypes2.default.func, 1263 | componentWillUnmount: _propTypes2.default.func, 1264 | componentDidCatch: _propTypes2.default.func, 1265 | componentToRender: _propTypes2.default.func.isRequired }; 1266 | 1267 | 1268 | var hookedLifeCycleComponent = function hookedLifeCycleComponent(Component) {var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};var 1269 | 1270 | onConstructor = 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | props.onConstructor,onRender = props.onRender,componentDidMount = props.componentDidMount,componentWillUnmount = props.componentWillUnmount,componentDidCatch = props.componentDidCatch,componentToRender = props.componentToRender,propsToPass = _objectWithoutProperties(props, ['onConstructor', 'onRender', 'componentDidMount', 'componentWillUnmount', 'componentDidCatch', 'componentToRender']); 1278 | return ( 1279 | _react2.default.createElement(ReactLifeCycleComponent, { 1280 | onConstructor: onConstructor, 1281 | onRender: onRender, 1282 | componentDidMount: componentDidMount, 1283 | componentWillUnmount: componentWillUnmount, 1284 | componentDidCatch: componentDidCatch, 1285 | componentToRender: function componentToRender() {return _react2.default.createElement(Component, _extends({}, propsToPass, { __source: { fileName: _jsxFileName, lineNumber: 517 } }));}, __source: { fileName: _jsxFileName, lineNumber: 511 } })); 1286 | 1287 | 1288 | };exports.hookedLifeCycleComponent = hookedLifeCycleComponent; 1289 | 1290 | var connectLifeCycleComponentWithStore = exports.connectLifeCycleComponentWithStore = function connectLifeCycleComponentWithStore(options) { 1291 | if (process.env.NODE_ENV !== 'production') { 1292 | if (options.serviceFunctions === undefined) { 1293 | throw new Error('connectLifeCycleComponentWithStore: You must define and export a serviceFunctions object in the model code in order to use this function.'); 1294 | } 1295 | } 1296 | var component = options.uiComponent; 1297 | var hookedLCC = function hookedLCC(props) {return hookedLifeCycleComponent(component, props);}; 1298 | var newOptions = _extends({}, options, { uiComponent: hookedLCC }); 1299 | return connectWithStoreBase(newOptions); 1300 | }; 1301 | 1302 | /* 1303 | options object parameter 1304 | 1305 | reducerKey - (required) The key in the redux store for this module 1306 | initialState - (required) The initial state that will be used in the reducer for initialization. 1307 | initialUIState - (optional) If this is specified then simpler-redux will build a mapStateToProps 1308 | function based on the keys in this object. 1309 | selectors - (optional) If this is specified then simpler-redux will build a mapStateToProps 1310 | function based on the selectors object. 1311 | selectorList - (Optionsl) An array of {selectors, keylist[list of selector keys]}. This allows 1312 | combining selectors from different modules into one in order to build a mapStateToProps that 1313 | includes key/values from other reducers keys including the component reducer selectors. If you 1314 | specify keylist then you can include only a subset of the selectors indtead of all of them. 1315 | serviceFunctions - (optional) If this is specified then simpler-redux will build a mapDispatchToProps 1316 | function based on the keys in this object. These will be the service functions exposed to the 1317 | the react component in the props. 1318 | serviceFunctionList` - An array of {serviceFunctions, keylist[list of serviceFunctions keys], 1319 | withStore}. This allows combining serviceFunctions from different modules into one in order 1320 | to build a mapDispatchToProps that includes key/values from other module serviceFunctions. 1321 | The keylist allows you to select only a subset of the associated service functions. The withStore 1322 | set to true will cause the store to be the first parameter for all the service functions when 1323 | called with the UI parameters following after. 1324 | noStoreParameterOnServiceFunctions = true (Optional) - By default, simpler-redux injects the store as the 1325 | first parameter when any service function is called by the UI. The UI parameters follow. 1326 | If this is set to true then simpler-redux will not do this store injection. 1327 | storeIsDefinedCallback(store, stateAccessors) - (Optional) If this is specified then simpler-redux will call 1328 | this function with the simpler redux store as a parameter when the store becomes available to the react 1329 | component. Use this to call the simpler-redux stateAccessors in order to gain access to 1330 | setState, getState and reducerState. 1331 | Example: 1332 | let setState, reducerState 1333 | export const storeIsDefinedCallback = (store, stateAccessors) => 1334 | ({setState, reducerState} = stateAccessors(store, reducerKey, initialState)) 1335 | isDynamicReducer - (Optional) This supports dynamic reducer loading. For this, simpler-redux 1336 | automatically takes care of building the reducer and loading it into the reducers object. 1337 | 1338 | Note: If you present any redux state in the react component then you must define and export either 1339 | a selectors object or an initialUIState object. Otherwise, you will not have any state in 1340 | the props of the react component. 1341 | */ 1342 | var connectWithStore = exports.connectWithStore = function connectWithStore(options) { 1343 | // First decide if the serviceFunctions object contains react lifecycle calls. 1344 | if (options.serviceFunctions !== undefined) { 1345 | var hasLifeCycle = Object.keys(options.serviceFunctions).some(function (e) {return ( 1346 | reactLifeCycleEvents[e] !== undefined);}); 1347 | 1348 | if (hasLifeCycle) { 1349 | return connectLifeCycleComponentWithStore(options); 1350 | } 1351 | } 1352 | // No react lifecycle calls. 1353 | return connectWithStoreBase(options); 1354 | };;(function () {var reactHotLoader = __webpack_require__(0).default;var leaveModule = __webpack_require__(0).leaveModule;if (!reactHotLoader) {return;}reactHotLoader.register(reactLifeCycleEvents, 'reactLifeCycleEvents', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allServiceFunctionsToPropsWithStore, 'allServiceFunctionsToPropsWithStore', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allServiceFunctionsToProps, 'allServiceFunctionsToProps', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allServiceFunctionsToPropsUsingServiceFunctionList, 'allServiceFunctionsToPropsUsingServiceFunctionList', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allStateToProps, 'allStateToProps', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allStateToPropsUsingSelectors, 'allStateToPropsUsingSelectors', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allStateToPropsUsingSelectorList, 'allStateToPropsUsingSelectorList', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(buildSelectorsFromUIState, 'buildSelectorsFromUIState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(allStateToPropsUsingUIState, 'allStateToPropsUsingUIState', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(buildCachedMapStateToProps, 'buildCachedMapStateToProps', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(connectWithStoreBase, 'connectWithStoreBase', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(ReactLifeCycleComponent, 'ReactLifeCycleComponent', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(hookedLifeCycleComponent, 'hookedLifeCycleComponent', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(connectLifeCycleComponentWithStore, 'connectLifeCycleComponentWithStore', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');reactHotLoader.register(connectWithStore, 'connectWithStore', 'C:/Users/Andrew/Documents/GitHub/simpler-redux/src/react-simpler-redux.js');leaveModule(module);})();; 1355 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)(module), __webpack_require__(3))) 1356 | 1357 | /***/ }), 1358 | /* 11 */ 1359 | /***/ (function(module, exports) { 1360 | 1361 | module.exports = require("react-redux"); 1362 | 1363 | /***/ }), 1364 | /* 12 */ 1365 | /***/ (function(module, exports) { 1366 | 1367 | module.exports = require("prop-types"); 1368 | 1369 | /***/ }), 1370 | /* 13 */ 1371 | /***/ (function(module, exports, __webpack_require__) { 1372 | 1373 | /** 1374 | * Copyright 2015, Yahoo! Inc. 1375 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 1376 | */ 1377 | (function (global, factory) { 1378 | true ? module.exports = factory() : 1379 | typeof define === 'function' && define.amd ? define(factory) : 1380 | (global.hoistNonReactStatics = factory()); 1381 | }(this, (function () { 1382 | 'use strict'; 1383 | 1384 | var REACT_STATICS = { 1385 | childContextTypes: true, 1386 | contextTypes: true, 1387 | defaultProps: true, 1388 | displayName: true, 1389 | getDefaultProps: true, 1390 | getDerivedStateFromProps: true, 1391 | mixins: true, 1392 | propTypes: true, 1393 | type: true 1394 | }; 1395 | 1396 | var KNOWN_STATICS = { 1397 | name: true, 1398 | length: true, 1399 | prototype: true, 1400 | caller: true, 1401 | callee: true, 1402 | arguments: true, 1403 | arity: true 1404 | }; 1405 | 1406 | var defineProperty = Object.defineProperty; 1407 | var getOwnPropertyNames = Object.getOwnPropertyNames; 1408 | var getOwnPropertySymbols = Object.getOwnPropertySymbols; 1409 | var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 1410 | var getPrototypeOf = Object.getPrototypeOf; 1411 | var objectPrototype = getPrototypeOf && getPrototypeOf(Object); 1412 | 1413 | return function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) { 1414 | if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components 1415 | 1416 | if (objectPrototype) { 1417 | var inheritedComponent = getPrototypeOf(sourceComponent); 1418 | if (inheritedComponent && inheritedComponent !== objectPrototype) { 1419 | hoistNonReactStatics(targetComponent, inheritedComponent, blacklist); 1420 | } 1421 | } 1422 | 1423 | var keys = getOwnPropertyNames(sourceComponent); 1424 | 1425 | if (getOwnPropertySymbols) { 1426 | keys = keys.concat(getOwnPropertySymbols(sourceComponent)); 1427 | } 1428 | 1429 | for (var i = 0; i < keys.length; ++i) { 1430 | var key = keys[i]; 1431 | if (!REACT_STATICS[key] && !KNOWN_STATICS[key] && (!blacklist || !blacklist[key])) { 1432 | var descriptor = getOwnPropertyDescriptor(sourceComponent, key); 1433 | try { // Avoid failures from read-only properties 1434 | defineProperty(targetComponent, key, descriptor); 1435 | } catch (e) {} 1436 | } 1437 | } 1438 | 1439 | return targetComponent; 1440 | } 1441 | 1442 | return targetComponent; 1443 | }; 1444 | }))); 1445 | 1446 | 1447 | /***/ }) 1448 | /******/ ]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simpler-redux", 3 | "version": "0.1.17", 4 | "description": "Redux made simple", 5 | "main": "lib/simpler-redux.js", 6 | "scripts": { 7 | "test:lib": "mocha --reporter spec --require babel-register ./test/**/*.js", 8 | "test": "cross-env NODE_ENV=mochaTesting mocha --require babel-register --require test/setup.js test/projectsetup.js test/mountapp.jsx test/test.js test/testApp.js --no-timeouts", 9 | "builddll:dev": "webpack --colors --config devtools/webpack.config.dlllib.dev.js", 10 | "build:lib": "webpack --config devtools/webpack.config.lib.js", 11 | "build": "npm run build:lib && npm run builddll:dev", 12 | "lint": "eslint src/**", 13 | "start": "cross-env NODE_ENV=development node devtools/devServer.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/AndrewBanks10/simpler-redux" 18 | }, 19 | "keywords": [ 20 | "simpler-redux", 21 | "library", 22 | "redux", 23 | "react" 24 | ], 25 | "author": { 26 | "name": "Andrew Banks", 27 | "email": "andrewbanks10@gmail.com" 28 | }, 29 | "license": "MIT", 30 | "homepage": "https://github.com/AndrewBanks10/simpler-redux", 31 | "devDependencies": { 32 | "babel-cli": "^6.24.1", 33 | "babel-core": "^6.25.0", 34 | "babel-eslint": "^8.2.3", 35 | "babel-loader": "^7.1.1", 36 | "babel-preset-env": "^1.6.1", 37 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 38 | "babel-plugin-transform-react-jsx": "^6.24.1", 39 | "babel-plugin-transform-react-jsx-source": "^6.22.0", 40 | "babel-preset-react": "^6.24.1", 41 | "babel-preset-react-optimize": "^1.0.1", 42 | "babel-preset-stage-0": "^6.24.1", 43 | "babel-register": "^6.24.1", 44 | "copyfiles": "^1.2.0", 45 | "cross-env": "^5.0.1", 46 | "enzyme": "^3.3.0", 47 | "enzyme-adapter-react-16": "^1.1.1", 48 | "eslint": "^4.19.1", 49 | "eslint-config-standard": "^11.0.0", 50 | "eslint-config-standard-react": "^5.0.0", 51 | "eslint-import-resolver-webpack": "^0.9.0", 52 | "eslint-plugin-babel": "^5.1.0", 53 | "eslint-plugin-import": "^2.11.0", 54 | "eslint-plugin-lodash": "^2.7.0", 55 | "eslint-plugin-node": "^6.0.1", 56 | "eslint-plugin-react": "^7.5.1", 57 | "eslint-plugin-promise": "^3.7.0", 58 | "eslint-plugin-standard": "^3.1.0", 59 | "ghooks": "^2.0.0", 60 | "html-webpack-plugin": "^2.29.0", 61 | "jsdom": "^11.10.0", 62 | "lodash": "^4.17.4", 63 | "mocha": "^3.4.2", 64 | "react-dom": "^16.3.2", 65 | "react-hot-loader": "^4.1.3", 66 | "react-test-renderer": "^16.3.2", 67 | "rimraf": "^2.6.1", 68 | "webpack": "^3.0.0", 69 | "webpack-dev-server": "^2.5.0" 70 | }, 71 | "dependencies": { 72 | "object-assign": "^4.1.1", 73 | "redux": "^3.7.2", 74 | "react": "^16.3.2", 75 | "react-redux": "^5.0.6", 76 | "hoist-non-react-statics": "^2.5.0" 77 | }, 78 | "config": { 79 | "host": "localhost", 80 | "port": 3000, 81 | "ghooks": { 82 | "pre-commit": "npm run lint" 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export * from './simpler-redux' 2 | export * from './react-simpler-redux' 3 | -------------------------------------------------------------------------------- /src/proxy.js: -------------------------------------------------------------------------------- 1 | const proxyDefined = () => 2 | typeof Proxy !== 'undefined' 3 | 4 | const getReducerKeyValue = (simplerReduxStore, reducerKey, key) => 5 | simplerReduxStore.getRState(reducerKey)[key] 6 | 7 | const setReducerKeyValue = (simplerReduxStore, reducerKey, key, value) => { 8 | simplerReduxStore.setRState(reducerKey, { [key]: value }) 9 | return true 10 | } 11 | 12 | const defineProxyGetSet = (obj, simplerReduxStore, reducerKey, key) => { 13 | Object.defineProperty( 14 | obj, 15 | key, { 16 | get: () => 17 | getReducerKeyValue(simplerReduxStore, reducerKey, key), 18 | set: value => 19 | setReducerKeyValue(simplerReduxStore, reducerKey, key, value) 20 | } 21 | ) 22 | } 23 | 24 | const simulateProxy = (simplerReduxStore, reducerKey, initialState) => 25 | Object.keys(initialState).reduce((obj, key) => { 26 | defineProxyGetSet(obj, simplerReduxStore, reducerKey, key) 27 | return obj 28 | }, {}) 29 | 30 | const getProxyHandler = reducerKey => { 31 | return { 32 | get: (simplerReduxStore, key) => 33 | getReducerKeyValue(simplerReduxStore, reducerKey, key), 34 | set: (simplerReduxStore, key, value) => 35 | setReducerKeyValue(simplerReduxStore, reducerKey, key, value) 36 | } 37 | } 38 | 39 | export default (simplerReduxStore, reducerKey, initialState) => { 40 | if (proxyDefined()) { 41 | return new Proxy(simplerReduxStore, getProxyHandler(reducerKey)) 42 | } 43 | return simulateProxy(simplerReduxStore, reducerKey, initialState) 44 | } 45 | -------------------------------------------------------------------------------- /src/react-simpler-redux.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import PropTypes from 'prop-types' 4 | import hoistStatics from 'hoist-non-react-statics' 5 | import { stateAccessors, srOptions } from './simpler-redux' 6 | import { shallowSubObjectCompare, shallowCopy } from './util' 7 | 8 | // React lifecycle events supported in the model code. 9 | const reactLifeCycleEvents = { 10 | onConstructor: 'onConstructor', 11 | onRender: 'onRender', 12 | componentDidMount: 'componentDidMount', 13 | componentWillUnmount: 'componentWillUnmount', 14 | componentDidCatch: 'componentDidCatch', 15 | componentToRender: 'componentToRender' 16 | } 17 | 18 | // 19 | // Builds a mapDispatchToProps function based on the service functions and adds the store as 20 | // the first parameter to each service function and the rest of the parameters given 21 | // by the react UI component when called in react. 22 | // 23 | export const allServiceFunctionsToPropsWithStore = serviceFunctions => { 24 | const keys = Object.keys(serviceFunctions) 25 | return (_dispatch, ownProps) => 26 | keys.reduce((obj, e) => { 27 | obj[e] = (...args) => serviceFunctions[e](ownProps.store, ...args) 28 | return obj 29 | }, {}) 30 | } 31 | 32 | // 33 | // Builds a mapDispatchToProps function based on the service functions and adds 34 | // all of the parameters given by the react UI component when called in react. 35 | // 36 | export const allServiceFunctionsToProps = serviceFunctions => 37 | () => ({ ...serviceFunctions }) 38 | 39 | // 40 | // Builds a mapDispatchToProps function based on a serviceFunctionList object. 41 | // This allows including service functions from various modules. 42 | // The keylist allows selecting only a subset of a module's service functions. 43 | // If keylist is not specified then all service functions will be included. 44 | // 45 | export const allServiceFunctionsToPropsUsingServiceFunctionList = serviceFunctionList => { 46 | serviceFunctionList = [...serviceFunctionList] 47 | return (_dispatch, ownProps) => 48 | serviceFunctionList.reduce((obj, e) => { 49 | const keylist = e.keylist ? e.keylist : Object.keys(e.serviceFunctions) 50 | keylist.forEach(key => { 51 | if (e.withStore) { 52 | obj[key] = (...args) => e.serviceFunctions[key](ownProps.store, ...args) 53 | } else { 54 | obj[key] = (...args) => e.serviceFunctions[key](...args) 55 | } 56 | }) 57 | return obj 58 | }, {}) 59 | } 60 | 61 | // 62 | // Builds a mapStateToProps function that returns the entire reducer state. 63 | // 64 | export const allStateToProps = reducerKey => { 65 | return state => { 66 | if (process.env.NODE_ENV !== 'production') { 67 | srOptions.mapStateToPropsCache({ 68 | msg: `mapStateToProps fast calculation at ${reducerKey}, cache not needed.`, 69 | reducerKey: reducerKey 70 | }) 71 | } 72 | return state[reducerKey] 73 | } 74 | } 75 | 76 | // 77 | // Builds a mapStateToProps function based on a selectors object. 78 | // 79 | export const allStateToPropsUsingSelectors = (selectors, reducerKey) => { 80 | const keys = Object.keys(selectors) 81 | if (!reducerKey) { 82 | return ( 83 | state => 84 | keys.reduce((obj, e) => { 85 | obj[e] = selectors[e](state) 86 | return obj 87 | }, {}) 88 | ) 89 | } 90 | // Implements a caching mechanism below such that if the state at the reducerKey did not change 91 | // then we return the previously calculated object. 92 | let prevState 93 | let lastresult 94 | return state => { 95 | if (prevState !== state[reducerKey]) { 96 | if (process.env.NODE_ENV !== 'production') { 97 | srOptions.mapStateToPropsCalc({ 98 | msg: `selector calculated ${reducerKey}`, 99 | prevState, 100 | nextState: state[reducerKey], 101 | reducerKey 102 | }) 103 | } 104 | prevState = state[reducerKey] 105 | lastresult = keys.reduce((obj, e) => { 106 | obj[e] = selectors[e](state) 107 | return obj 108 | }, {}) 109 | } else { 110 | if (process.env.NODE_ENV !== 'production') { 111 | srOptions.mapStateToPropsCache({ 112 | msg: `selector cache ${reducerKey}`, 113 | prevState, 114 | nextState: state[reducerKey], 115 | reducerKey 116 | }) 117 | } 118 | } 119 | return lastresult 120 | } 121 | } 122 | 123 | // 124 | // Builds a mapStateToProps function based on a selectorList object. 125 | // This allows including selectors from various modules. 126 | // The keylist allows selecting only a subset of a module's selectors. 127 | // If keylist is not specified then all selectors will be included. 128 | // 129 | export const allStateToPropsUsingSelectorList = (selectorList, componentName) => { 130 | selectorList = [...selectorList] 131 | let useCache = true 132 | let reducerKeys = [] 133 | for (let i = 0; i < selectorList.length; ++i) { 134 | if (selectorList[i].keylist === undefined) { 135 | selectorList[i].keylist = Object.keys(selectorList[i].selectors) 136 | } 137 | const reducerKey = selectorList[i].reducerKey 138 | if (reducerKey) { 139 | reducerKeys.push(reducerKey) 140 | } else { 141 | useCache = false 142 | } 143 | } 144 | if (componentName === undefined) { 145 | componentName = reducerKeys.toString() 146 | } 147 | if (useCache) { 148 | let prevStates = {} 149 | let lastResult 150 | return state => { 151 | if (!shallowSubObjectCompare(state, prevStates, reducerKeys)) { 152 | if (process.env.NODE_ENV !== 'production') { 153 | srOptions.mapStateToPropsCalc({ 154 | msg: `Selector List calculated ${componentName}.`, 155 | nextState: shallowCopy(state, reducerKeys), 156 | prevState: prevStates, 157 | reducerKeys 158 | }) 159 | } 160 | prevStates = shallowCopy(state, reducerKeys) 161 | lastResult = selectorList.reduce((obj, e) => { 162 | e.keylist.forEach(key => { 163 | obj[key] = e.selectors[key](state) 164 | }) 165 | return obj 166 | }, {}) 167 | } else { 168 | if (process.env.NODE_ENV !== 'production') { 169 | srOptions.mapStateToPropsCache({ 170 | msg: `Selector List cache ${componentName}.`, 171 | nextState: shallowCopy(state, reducerKeys), 172 | prevState: prevStates, 173 | reducerKeys 174 | }) 175 | } 176 | } 177 | return lastResult 178 | } 179 | } 180 | reducerKeys = [] 181 | return state => 182 | selectorList.reduce((obj, e) => { 183 | e.keylist.forEach(key => { 184 | obj[key] = e.selectors[key](state) 185 | }) 186 | return obj 187 | }, {}) 188 | } 189 | 190 | // 191 | // Builds a model selectors object from either initialUIState or initialState. 192 | // initialUIState should only contain keys that you want in the 193 | // props of the react component. 194 | // This is not for specialized selectors for the UI that require conjunctions or 195 | // selectors from other modules, etc. 196 | // It is only for simple selectors of the nature state => state[reducerKey][stateKey] 197 | // 198 | export const buildSelectorsFromUIState = (reducerKey, initialState) => { 199 | const keys = Object.keys(initialState) 200 | return keys.reduce((obj, e) => { 201 | obj[e] = state => state[reducerKey][e] 202 | return obj 203 | }, {}) 204 | } 205 | 206 | // 207 | // Builds a mapStateToProps function that returns key/UI State pairs 208 | // 209 | const allStateToPropsUsingUIState = (reducerKey, initialUIState) => { 210 | const keys = Object.keys(initialUIState) 211 | let prevState 212 | let lastResult 213 | return state => { 214 | if (state[reducerKey] !== prevState) { 215 | if (process.env.NODE_ENV !== 'production') { 216 | srOptions.mapStateToPropsCalc({ 217 | msg: `State props calculated at ${reducerKey}.`, 218 | nextState: state[reducerKey], 219 | prevState: prevState, 220 | reducerKey 221 | }) 222 | } 223 | prevState = state[reducerKey] 224 | lastResult = shallowCopy(state[reducerKey], keys) 225 | } else { 226 | if (process.env.NODE_ENV !== 'production') { 227 | srOptions.mapStateToPropsCache({ 228 | msg: `State props cache at ${reducerKey}.`, 229 | nextState: state[reducerKey], 230 | prevState: prevState, 231 | reducerKey 232 | }) 233 | } 234 | } 235 | return lastResult 236 | } 237 | } 238 | 239 | const buildCachedMapStateToProps = (mapStateToProps, mapStateToPropsReducerKeys, componentName) => { 240 | if (!Array.isArray(mapStateToPropsReducerKeys)) { 241 | mapStateToPropsReducerKeys = [mapStateToPropsReducerKeys] 242 | } 243 | if (componentName === undefined) { 244 | componentName = mapStateToPropsReducerKeys.toString() 245 | } 246 | let prevStates = {} 247 | let lastResult 248 | return state => { 249 | if (!shallowSubObjectCompare(state, prevStates, mapStateToPropsReducerKeys)) { 250 | if (process.env.NODE_ENV !== 'production') { 251 | srOptions.mapStateToPropsCalc({ 252 | msg: `mapStateToProps calculated ${componentName}.`, 253 | nextState: shallowCopy(state, mapStateToPropsReducerKeys), 254 | prevState: prevStates, 255 | reducerKeys: mapStateToPropsReducerKeys 256 | }) 257 | } 258 | prevStates = shallowCopy(state, mapStateToPropsReducerKeys) 259 | lastResult = mapStateToProps(state) 260 | } else { 261 | if (process.env.NODE_ENV !== 'production') { 262 | srOptions.mapStateToPropsCache({ 263 | msg: `mapStateToProps cache ${componentName}.`, 264 | nextState: shallowCopy(state, mapStateToPropsReducerKeys), 265 | prevState: prevStates, 266 | reducerKeys: mapStateToPropsReducerKeys 267 | }) 268 | } 269 | } 270 | return lastResult 271 | } 272 | } 273 | 274 | const connectWithStoreBase = ( 275 | options 276 | ) => { 277 | let { 278 | uiComponent, 279 | reduxOptions, 280 | storeIsDefinedCallback, 281 | reducerKey, 282 | isDynamicReducer, 283 | initialState, 284 | mapStateToProps, 285 | mapDispatchToProps, 286 | serviceFunctions, 287 | serviceFunctionList, 288 | selectors, 289 | selectorList, 290 | initialUIState, 291 | noStoreParameterOnServiceFunctions, 292 | mergeProps, 293 | mapStateToPropsReducerKeys, 294 | componentName 295 | } = options 296 | 297 | if (process.env.NODE_ENV !== 'production') { 298 | if (uiComponent === undefined) { 299 | throw new Error(`connectWithStore: options.uiComponent cannot be undefined, reducerKey=${reducerKey}.`) 300 | } 301 | if (storeIsDefinedCallback && typeof storeIsDefinedCallback !== 'function') { 302 | throw new Error(`connectWithStore: options.storeIsDefinedCallback must be a function, reducerKey=${reducerKey}.`) 303 | } 304 | if (selectors !== undefined && initialUIState !== undefined) { 305 | throw new Error(`connectWithStore: Cannot export both selectors and initialUIState, reducerKey=${reducerKey}.`) 306 | } 307 | let displayName = '' 308 | if (uiComponent !== undefined) { 309 | if (uiComponent.displayName !== undefined) { 310 | displayName = uiComponent.displayName 311 | } else { 312 | displayName = uiComponent.name 313 | } 314 | } 315 | if (selectorList !== undefined) { 316 | selectorList.forEach(e => { 317 | if (e.keylist !== undefined) { 318 | e.keylist.forEach(key => { 319 | if (typeof e.selectors[key] !== 'function') { 320 | throw new Error(`connectWithStore ${displayName}: The selectors key ${key} is not in the selectors ${e.keylist.toString()}.`) 321 | } 322 | }) 323 | } 324 | }) 325 | } 326 | if (serviceFunctionList !== undefined) { 327 | serviceFunctionList.forEach(e => { 328 | if (e.keylist !== undefined) { 329 | e.keylist.forEach(key => { 330 | if (typeof e.serviceFunctions[key] !== 'function') { 331 | throw new Error(`connectWithStore ${displayName}: The serviceFunctions key ${key} is not in the serviceFunctionList ${e.keylist.toString()}.`) 332 | } 333 | }) 334 | } 335 | }) 336 | } 337 | } 338 | 339 | if (componentName === undefined) { 340 | componentName = reducerKey 341 | } 342 | 343 | // Default initialState (reducer state) to initialUIState (component props state). 344 | if (initialState === undefined) { 345 | initialState = initialUIState 346 | } 347 | 348 | // Default initialUIState (component props state) to initialState (reducer state). 349 | if (initialUIState === undefined) { 350 | initialUIState = initialState 351 | } 352 | 353 | const withRef = reduxOptions && reduxOptions.withRef 354 | if (initialState !== undefined) { 355 | initialState = { ...initialState } 356 | } 357 | 358 | // If mapStateToProps is defined by the consumer then keep it no matter what. 359 | if (mapStateToProps !== undefined) { 360 | if (mapStateToPropsReducerKeys !== undefined) { 361 | mapStateToProps = buildCachedMapStateToProps(mapStateToProps, mapStateToPropsReducerKeys, componentName) 362 | } 363 | } else { 364 | if (selectorList !== undefined) { 365 | mapStateToProps = allStateToPropsUsingSelectorList(selectorList, componentName) 366 | } else if (selectors !== undefined) { 367 | mapStateToProps = allStateToPropsUsingSelectors(selectors, reducerKey) 368 | } else if (reducerKey !== undefined && initialUIState !== undefined) { 369 | // This is for efficiency. initialUIState and initialState are the same so 370 | // mapStateToProps simply returns the entire reducerKey state. 371 | if (Object.keys(initialUIState).length === Object.keys(initialState).length) { 372 | mapStateToProps = allStateToProps(reducerKey) 373 | } else { 374 | mapStateToProps = allStateToPropsUsingUIState(reducerKey, initialUIState) 375 | } 376 | } 377 | } 378 | 379 | // If mapDispatchToProps is defined by the consumer then keep it no matter what. 380 | if (mapDispatchToProps === undefined) { 381 | if (serviceFunctionList !== undefined) { 382 | mapDispatchToProps = allServiceFunctionsToPropsUsingServiceFunctionList(serviceFunctionList) 383 | } else if (serviceFunctions !== undefined) { 384 | if (noStoreParameterOnServiceFunctions) { 385 | mapDispatchToProps = allServiceFunctionsToProps(serviceFunctions) 386 | } else { 387 | mapDispatchToProps = allServiceFunctionsToPropsWithStore(serviceFunctions) 388 | } 389 | } 390 | } 391 | 392 | // Call the react-redux connect. 393 | const ConnectedComponent = connect( 394 | mapStateToProps, 395 | mapDispatchToProps, 396 | mergeProps, 397 | reduxOptions 398 | )(uiComponent) 399 | 400 | class HOC extends React.Component { 401 | constructor (props, context) { 402 | super(props, context) 403 | // Handles the dynamic loading of the reducer. 404 | if (isDynamicReducer !== false && this.context.store.isDynamicReducerLoading()) { 405 | // This will build the reducer and add it to the reducers object. 406 | if (reducerKey !== undefined && initialState !== undefined) { 407 | this.context.store.addReducer(reducerKey, initialState) 408 | } 409 | } 410 | // Handles a callback for the consumer to cache and/or use the store. 411 | if (storeIsDefinedCallback) { 412 | storeIsDefinedCallback(this.context.store, stateAccessors) 413 | } 414 | this.setWrappedInstance = this.setWrappedInstance.bind(this) 415 | } 416 | // Support the redux connect getWrappedInstance out of this HOC. 417 | // This way, the consumer does nothing different when using this HOC 418 | // vs redux connected components when handling refs. 419 | setWrappedInstance (ref) { 420 | if (!withRef) { 421 | return 422 | } 423 | // Refer to the original instance of the component wrapped with connect. 424 | if (ref) { 425 | if (process.env.NODE_ENV !== 'production') { 426 | if (typeof ref.getWrappedInstance !== 'function') { 427 | console.log('There is something wrong with redux connect.') 428 | return 429 | } 430 | } 431 | this.wrappedInstance = ref.getWrappedInstance() 432 | } 433 | } 434 | getWrappedInstance () { 435 | if (process.env.NODE_ENV !== 'production') { 436 | if (this.wrappedInstance === undefined) { 437 | console.log('The getWrappedInstance return is undefined. Did you use the withRef: true option?') 438 | } 439 | } 440 | return this.wrappedInstance 441 | } 442 | render () { 443 | // Add the store to the props of the redux connected component so that it can be referenced 444 | // in mapDispatchToProps with ownProps. 445 | return ( 446 | 451 | ) 452 | } 453 | } 454 | 455 | HOC.displayName = `connectWithStore(${ConnectedComponent.displayName || ConnectedComponent.name})` 456 | // Opt in for the context. 457 | HOC.contextTypes = { 458 | store: PropTypes.object 459 | } 460 | return hoistStatics(HOC, ConnectedComponent) 461 | } 462 | 463 | // 464 | // This supports moving the react life cycle events into the model/business code serviceFunctions functions object. 465 | // 466 | class ReactLifeCycleComponent extends React.Component { 467 | constructor (props) { 468 | super(props) 469 | this.runFunction = this.runFunction.bind(this) 470 | this.runFunction(this.props.onConstructor) 471 | } 472 | runFunction (func, args = []) { 473 | return func ? func.call(this, ...args) : null 474 | } 475 | componentDidMount () { 476 | this.runFunction(this.props.componentDidMount) 477 | } 478 | componentWillUnmount () { 479 | this.runFunction(this.props.componentWillUnmount) 480 | } 481 | componentDidCatch (...args) { 482 | this.runFunction(this.props.componentDidCatch, args) 483 | } 484 | render () { 485 | this.runFunction(this.props.onRender) 486 | // Render prop 487 | return this.props.componentToRender() 488 | } 489 | } 490 | 491 | ReactLifeCycleComponent.propTypes = { 492 | onConstructor: PropTypes.func, 493 | onRender: PropTypes.func, 494 | componentDidMount: PropTypes.func, 495 | componentWillUnmount: PropTypes.func, 496 | componentDidCatch: PropTypes.func, 497 | componentToRender: PropTypes.func.isRequired 498 | } 499 | 500 | export const hookedLifeCycleComponent = (Component, props = {}) => { 501 | const { 502 | onConstructor, 503 | onRender, 504 | componentDidMount, 505 | componentWillUnmount, 506 | componentDidCatch, 507 | componentToRender, 508 | ...propsToPass 509 | } = props 510 | return ( 511 | } 518 | /> 519 | ) 520 | } 521 | 522 | export const connectLifeCycleComponentWithStore = options => { 523 | if (process.env.NODE_ENV !== 'production') { 524 | if (options.serviceFunctions === undefined) { 525 | throw new Error('connectLifeCycleComponentWithStore: You must define and export a serviceFunctions object in the model code in order to use this function.') 526 | } 527 | } 528 | const component = options.uiComponent 529 | const hookedLCC = props => hookedLifeCycleComponent(component, props) 530 | const newOptions = { ...options, uiComponent: hookedLCC } 531 | return connectWithStoreBase(newOptions) 532 | } 533 | 534 | /* 535 | options object parameter 536 | 537 | reducerKey - (required) The key in the redux store for this module 538 | initialState - (required) The initial state that will be used in the reducer for initialization. 539 | initialUIState - (optional) If this is specified then simpler-redux will build a mapStateToProps 540 | function based on the keys in this object. 541 | selectors - (optional) If this is specified then simpler-redux will build a mapStateToProps 542 | function based on the selectors object. 543 | selectorList - (Optionsl) An array of {selectors, keylist[list of selector keys]}. This allows 544 | combining selectors from different modules into one in order to build a mapStateToProps that 545 | includes key/values from other reducers keys including the component reducer selectors. If you 546 | specify keylist then you can include only a subset of the selectors indtead of all of them. 547 | serviceFunctions - (optional) If this is specified then simpler-redux will build a mapDispatchToProps 548 | function based on the keys in this object. These will be the service functions exposed to the 549 | the react component in the props. 550 | serviceFunctionList` - An array of {serviceFunctions, keylist[list of serviceFunctions keys], 551 | withStore}. This allows combining serviceFunctions from different modules into one in order 552 | to build a mapDispatchToProps that includes key/values from other module serviceFunctions. 553 | The keylist allows you to select only a subset of the associated service functions. The withStore 554 | set to true will cause the store to be the first parameter for all the service functions when 555 | called with the UI parameters following after. 556 | noStoreParameterOnServiceFunctions = true (Optional) - By default, simpler-redux injects the store as the 557 | first parameter when any service function is called by the UI. The UI parameters follow. 558 | If this is set to true then simpler-redux will not do this store injection. 559 | storeIsDefinedCallback(store, stateAccessors) - (Optional) If this is specified then simpler-redux will call 560 | this function with the simpler redux store as a parameter when the store becomes available to the react 561 | component. Use this to call the simpler-redux stateAccessors in order to gain access to 562 | setState, getState and reducerState. 563 | Example: 564 | let setState, reducerState 565 | export const storeIsDefinedCallback = (store, stateAccessors) => 566 | ({setState, reducerState} = stateAccessors(store, reducerKey, initialState)) 567 | isDynamicReducer - (Optional) This supports dynamic reducer loading. For this, simpler-redux 568 | automatically takes care of building the reducer and loading it into the reducers object. 569 | 570 | Note: If you present any redux state in the react component then you must define and export either 571 | a selectors object or an initialUIState object. Otherwise, you will not have any state in 572 | the props of the react component. 573 | */ 574 | export const connectWithStore = options => { 575 | // First decide if the serviceFunctions object contains react lifecycle calls. 576 | if (options.serviceFunctions !== undefined) { 577 | const hasLifeCycle = Object.keys(options.serviceFunctions).some(e => 578 | reactLifeCycleEvents[e] !== undefined 579 | ) 580 | if (hasLifeCycle) { 581 | return connectLifeCycleComponentWithStore(options) 582 | } 583 | } 584 | // No react lifecycle calls. 585 | return connectWithStoreBase(options) 586 | } 587 | -------------------------------------------------------------------------------- /src/simpler-redux.js: -------------------------------------------------------------------------------- 1 | /* 2 | Written by Andrew Banks. MIT license. 3 | */ 4 | import { createStore as createReduxStore, combineReducers } from 'redux' 5 | import { defaultOptions } from './util' 6 | import getReducerKeyProxy from './proxy' 7 | 8 | const simplerReduxReducerKey = '@@@@@srReducerKey' 9 | const simplerReduxObjectToMergeKey = '@@@@@srObjectToMergeKey' 10 | 11 | const objectType = obj => Object.prototype.toString.call(obj).slice(8, -1) 12 | const isObjectType = obj => objectType(obj) === 'Object' 13 | 14 | export let srOptions 15 | 16 | const makeSetRState = reduxStore => { 17 | return (reducerKey, objToMerge, type) => { 18 | if (type === undefined) { 19 | type = reducerKey 20 | } 21 | if (process.env.NODE_ENV !== 'production') { 22 | if (typeof reducerKey !== 'string') { 23 | throw new Error('setRState: The first argument must be a string.') 24 | } 25 | if (!isObjectType(objToMerge)) { 26 | throw new Error('setRState: The second argument must be a primitive object type.') 27 | } 28 | if (typeof type !== 'string') { 29 | throw new Error('setRState: The third argument must be a string.') 30 | } 31 | } 32 | 33 | reduxStore.dispatch({ 34 | [simplerReduxReducerKey]: reducerKey, 35 | [simplerReduxObjectToMergeKey]: objToMerge, 36 | type 37 | }) 38 | 39 | reduxStore.listeners.forEach(listenerObj => { 40 | listenerObj.listener(reducerKey, objToMerge, type) 41 | }) 42 | } 43 | } 44 | 45 | const makeGetRState = reduxStore => { 46 | return reducerKey => { 47 | const state = reduxStore.getState()[reducerKey] 48 | if (process.env.NODE_ENV !== 'production') { 49 | if (typeof reducerKey !== 'string') { 50 | throw new Error('getRState: The first argument must be a string.') 51 | } 52 | if (state === undefined) { 53 | throw new Error(`The reducerKey state at ${reducerKey} is undefined. Did you forget to export an initialUIState or initialState from your model code?`) 54 | } 55 | } 56 | return state 57 | } 58 | } 59 | 60 | // These listeners do what redux subscribers should have done. It gives the reducerKey being modified 61 | // so that a listener can quickly decide if it is concerned about changes in that reducerKey. 62 | const addListener = store => { 63 | return listener => { 64 | if (process.env.NODE_ENV !== 'production') { 65 | if (typeof listener !== 'function') { 66 | throw new Error('addListener: The first argument must be a function.') 67 | } 68 | } 69 | const id = store.listenerId++ 70 | store.listeners.push({ listener, id }) 71 | // Return a function that will remove this listener. 72 | return () => { 73 | let i = 0 74 | for (; i < store.listeners.length && store.listeners[i].id !== id; ++i); 75 | if (i < store.listeners.length) { 76 | store.listeners.splice(i, 1) 77 | } 78 | } 79 | } 80 | } 81 | 82 | // 83 | // Call this to generate your reducer. 84 | // 85 | export const generalReducer = (reducerKey, initialState) => { 86 | if (process.env.NODE_ENV !== 'production') { 87 | if (reducerKey === undefined) { 88 | throw new Error('generalReducer: reducerKey must be defined.') 89 | } 90 | if (initialState === undefined) { 91 | throw new Error('generalReducer: initialState must be defined.') 92 | } 93 | } 94 | initialState = { ...initialState } 95 | return (state = initialState, action) => { 96 | if (action[simplerReduxReducerKey] === reducerKey) { 97 | return { ...state, ...action[simplerReduxObjectToMergeKey] } 98 | } 99 | return state 100 | } 101 | } 102 | 103 | // Allows dynamic loading of simpler redux mvc components and their associated reducers. 104 | const buildAddReducer = (store, preloadedState) => { 105 | return (reducerKey, initialState) => { 106 | if (process.env.NODE_ENV !== 'production') { 107 | if (reducerKey === undefined) { 108 | throw new Error('addReducer: The first argument (reducerKey) must be defined.') 109 | } 110 | if (initialState === undefined) { 111 | throw new Error('addReducer: The second argument (initialState) must be defined.') 112 | } 113 | } 114 | // First load the initial state for the reducer. This is the developer defined initial state. 115 | let state = { ...initialState } 116 | // Then load the preloadedState for the keys that exist in the initial state. 117 | // Therefore, keys in preloadedState[reducerKey] that are not part of the current 118 | // state shape in initialState are considered deprecated and are ignored. Otherwise, 119 | // redux tosses a warning flag. 120 | const preloadedStateAtReducerKey = preloadedState[reducerKey] 121 | if (preloadedStateAtReducerKey !== undefined) { 122 | Object.keys(preloadedStateAtReducerKey).forEach(key => { 123 | if (state[key] !== undefined) { 124 | state[key] = preloadedStateAtReducerKey[key] 125 | } 126 | }) 127 | } 128 | // One reducer with no typical redux reducers. 129 | if (store.isOneReducer) { 130 | let currentWholeState = store.getState() 131 | // Do not set initialState on HMR. 132 | if (currentWholeState === undefined || currentWholeState[reducerKey] === undefined) { 133 | // Set the initialState state at the reducerKey. 134 | store.setRState(reducerKey, state) 135 | } 136 | return 137 | } 138 | // Generate the reducer for reducerKey. 139 | const reducer = generalReducer(reducerKey, state) 140 | // Add the reducer to the current list. 141 | store.currentReducersObject = { ...store.currentReducersObject, [reducerKey]: reducer } 142 | // Replace the redux reducers with the new list. 143 | store.replaceReducer(combineReducers(store.currentReducersObject)) 144 | } 145 | } 146 | 147 | // 148 | // This must be called with the redux store as a parameter after a createStore. 149 | // Then use the return of this function in the react-redux Provider element as the store. 150 | // If you pass in a rootReducersObject then you can use simpler-redux dynamic loading of reducers. 151 | // Note: rootReducersObject is the actual reducers object and not the combineReducers output. 152 | // If you want only dynamic reducers, use state => state (null reducer) for the redux createStore reducer 153 | // and { } as the rootReducersObject for the call below. 154 | // If you use rootReducersObject then you should also pass in preloadedState (if it exists). 155 | // options 156 | // 1) isDynamicReducer - Default to dynamic reducer loading for react components so that you do not have to specify isDynamicReducer in each component module. 157 | // 158 | export const registerSimplerRedux = ( 159 | reduxStore, 160 | rootReducersObject, 161 | preloadedState = {}, 162 | options = {} 163 | ) => { 164 | srOptions = defaultOptions(options) 165 | let wrappedReduxStore = Object.create(reduxStore) 166 | wrappedReduxStore.setRState = makeSetRState(wrappedReduxStore) 167 | wrappedReduxStore.getRState = makeGetRState(wrappedReduxStore) 168 | wrappedReduxStore.addListener = addListener(wrappedReduxStore) 169 | wrappedReduxStore.listenerId = 0 170 | wrappedReduxStore.listeners = [] 171 | // Support for dynamic reducer loading. 172 | if (rootReducersObject !== undefined) { 173 | wrappedReduxStore.isDynamicReducerLoading = () => true 174 | wrappedReduxStore.currentReducersObject = { ...rootReducersObject } 175 | wrappedReduxStore.addReducer = buildAddReducer(wrappedReduxStore, { ...preloadedState }) 176 | } else { 177 | wrappedReduxStore.isDynamicReducerLoading = () => false 178 | if (process.env.NODE_ENV !== 'production') { 179 | wrappedReduxStore.addReducer = () => { 180 | throw new Error('To call addReducer, you must specify a rootReducersObject in the 2nd argument of registerSimplerRedux which can be just {}.') 181 | } 182 | } 183 | } 184 | return wrappedReduxStore 185 | } 186 | 187 | // 188 | // One reducer is not compatible with existing redux code. Must be all simpler-redux. 189 | // This is the only reducer called for all state transitions. 190 | // 191 | const oneReducer = (state = {}, action) => { 192 | const reducerKey = action[simplerReduxReducerKey] 193 | const objToMerge = action[simplerReduxObjectToMergeKey] 194 | // This is some redux thing, not from our setRState. 195 | if (reducerKey === undefined) { 196 | return state 197 | } 198 | // Must change the upper level redux state pointer or redux does not recognize a state change. 199 | state = { ...state } 200 | // Merge the incoming reducerKey state at the reducerKey 201 | state[reducerKey] = {...state[reducerKey], ...objToMerge} 202 | return state 203 | } 204 | 205 | // This cannot be used with redux reducers. 206 | export const createStore = (preloadedState, enhancer, options = {}) => { 207 | const reduxStore = createReduxStore( 208 | oneReducer, 209 | preloadedState, 210 | enhancer 211 | ) 212 | const wrappedReduxStore = registerSimplerRedux( 213 | reduxStore, 214 | {}, 215 | preloadedState, 216 | options 217 | ) 218 | wrappedReduxStore.isOneReducer = true 219 | return wrappedReduxStore 220 | } 221 | 222 | // This makes it easier to access reducerKey state previously given a reducerKey. 223 | export const getStateFunction = reducerKey => 224 | (state, key) => 225 | state[reducerKey][key] 226 | 227 | // This makes it easier to set reducerKey state previously given a reducerKey. 228 | export const setStateFunction = reducerKey => 229 | (store, mergeState, type) => 230 | store.setRState(reducerKey, mergeState, type) 231 | 232 | // Use this to generate shared module keys. 233 | export const makeSharedModuleKeyName = (key, options) => 234 | `${key}${options.id}` 235 | 236 | // Sifts out dynamically loaded reducer keys from the preloaded state in order to avoid 237 | // a redux warning. Use the return of this function to pass into the redux createStore. 238 | export const reducersPreloadedState = (reducersObject, preloadedState) => 239 | Object.keys(preloadedState).reduce((obj, key) => { 240 | if (reducersObject[key] !== undefined) { 241 | obj[key] = preloadedState[key] 242 | } 243 | return obj 244 | }, {}) 245 | 246 | const getState = (store, reducerKey) => 247 | () => store.getRState(reducerKey) 248 | 249 | const setState = (store, reducerKey) => 250 | (mergeState, type) => { 251 | if (process.env.NODE_ENV !== 'production') { 252 | if (!isObjectType(mergeState)) { 253 | throw new Error('setState: The first argument must be a primitive object type.') 254 | } 255 | } 256 | store.setRState(reducerKey, mergeState, type) 257 | } 258 | 259 | // 260 | // Only call this in the storeIsDefinedCallback sent into connectWithStore above. 261 | // Use the store parameter provided in connectWithStore along with the reducerKey 262 | // in the module. 263 | // 264 | export const stateAccessors = (store, reducerKey, initialState) => { 265 | if (process.env.NODE_ENV !== 'production') { 266 | if (store === undefined) { 267 | throw new Error('The first parameter (store) to stateAccessors must be defined.') 268 | } 269 | if (typeof reducerKey !== 'string') { 270 | throw new Error('The second parameter (reducerKey) to stateAccessors must be a string.') 271 | } 272 | } 273 | let ret = { 274 | getState: getState(store, reducerKey), 275 | setState: setState(store, reducerKey) 276 | } 277 | 278 | if (initialState !== undefined) { 279 | ret.reducerState = getReducerKeyProxy(store, reducerKey, initialState) 280 | } 281 | 282 | return ret 283 | } 284 | 285 | // Creates general purpose module data under a redux reducerKey. 286 | // A redux store must be imported from your redux createStore module for the argument below. 287 | export const createModuleData = (store, reducerKey, initialState) => { 288 | if (process.env.NODE_ENV !== 'production') { 289 | if (!store.isDynamicReducerLoading()) { 290 | throw new Error('To call createModuleData, you must specify a rootReducersObject in the 2nd argument of registerSimplerRedux which can be just {}.') 291 | } 292 | if (store === undefined) { 293 | throw new Error('The first parameter (store) to createModuleData must be defined.') 294 | } 295 | if (typeof reducerKey !== 'string') { 296 | throw new Error('The seccond parameter (reducerKey) to createModuleData must a string.') 297 | } 298 | if (initialState === undefined) { 299 | throw new Error('The third parameter (initialState) to createModuleData must be defined.') 300 | } 301 | } 302 | store.addReducer(reducerKey, initialState) 303 | return stateAccessors(store, reducerKey, initialState) 304 | } 305 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | let totalMSTPCalcs = 0 2 | let totalMSTPCache = 0 3 | let useDefaultMSTPCacheOnlyLogging = false 4 | 5 | const showMessage = msg => 6 | console.log(msg) 7 | 8 | export const shallowSubObjectCompare = (obj, subObj, subObjectkeys) => { 9 | const len = subObjectkeys.length 10 | for (let i = 0; i < len; ++i) { 11 | const key = subObjectkeys[i] 12 | if (subObj[key] !== obj[key]) { 13 | return false 14 | } 15 | } 16 | return true 17 | } 18 | 19 | export const shallowCopy = (obj, copykeys) => { 20 | if (copykeys === undefined) { 21 | copykeys = Object.keys(obj) 22 | } 23 | let subObj = {} 24 | const len = copykeys.length 25 | for (let i = 0; i < len; ++i) { 26 | const key = copykeys[i] 27 | subObj[key] = obj[key] 28 | } 29 | return subObj 30 | } 31 | 32 | const displayMessage = obj => { 33 | if (!obj.prevState) { 34 | showMessage(obj.msg) 35 | } else { 36 | showMessage(`${obj.msg} 37 | prevState: %onextState: %o`, 38 | obj.prevState, 39 | obj.nextState 40 | ) 41 | } 42 | 43 | // Give the stats as performance feedback to developers. 44 | showMessage(`totalMSTPCalcs=${totalMSTPCalcs}, totalMSTPCache=${totalMSTPCache}`) 45 | } 46 | 47 | const mapStateToPropsCache = obj => { 48 | totalMSTPCache++ 49 | if (useDefaultMSTPCacheOnlyLogging) { 50 | showMessage(`totalMSTPCache=${totalMSTPCache}`) 51 | } else { 52 | displayMessage(obj) 53 | } 54 | } 55 | 56 | const mapStateToPropsCalc = obj => { 57 | totalMSTPCalcs++ 58 | if (useDefaultMSTPCacheOnlyLogging) { 59 | showMessage(`totalMSTPCalcs=${totalMSTPCalcs}`) 60 | } else { 61 | displayMessage(obj) 62 | } 63 | } 64 | 65 | export const defaultOptions = options => { 66 | if (options === undefined) { 67 | return 68 | } 69 | options = { ...options } 70 | useDefaultMSTPCacheOnlyLogging = options.useDefaultMSTPCacheOnlyLogging 71 | if (!options.mapStateToPropsCache) { 72 | options.mapStateToPropsCache = () => { } 73 | } 74 | if (!options.mapStateToPropsCalc) { 75 | options.mapStateToPropsCalc = () => { } 76 | } 77 | if (options.useDefaultMSTPCacheLogging || options.useDefaultMSTPCacheOnlyLogging) { 78 | options.mapStateToPropsCalc = mapStateToPropsCalc 79 | options.mapStateToPropsCache = mapStateToPropsCache 80 | } 81 | return options 82 | } 83 | -------------------------------------------------------------------------------- /test/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import store from './reduxstore' 4 | import Counter from './Counter' 5 | import Counter2 from './Counter2' 6 | import Counter3 from './Counter3' 7 | import Counter4 from './Counter4' 8 | import Counter5 from './Counter5' 9 | import Counter6 from './Counter6' 10 | import Counter7 from './Counter7' 11 | import Counter8 from './Counter8' 12 | import WrapCounter1 from './WrapCounter1' 13 | 14 | export default () => 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /test/Counter/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore, allStateToProps } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ 6 | uiComponent, 7 | ...modelDefinition, 8 | mapStateToProps: allStateToProps(modelDefinition.reducerKey) 9 | }) 10 | -------------------------------------------------------------------------------- /test/Counter/model.js: -------------------------------------------------------------------------------- 1 | import { getStateFunction, setStateFunction } from '../../test/test' 2 | import { consoleSuccess } from '../util' 3 | export const reducerKey = 'counter.4' 4 | 5 | const getRState = getStateFunction(reducerKey) 6 | const setRState = setStateFunction(reducerKey) 7 | 8 | const counter1Key = 'counter1' 9 | const counter2Key = 'counter2' 10 | const counter3Key = 'counter3' 11 | 12 | const initialUIState = { 13 | [counter1Key]: 10, 14 | [counter2Key]: 10, 15 | [counter3Key]: 10 16 | } 17 | 18 | export const initialState = initialUIState 19 | 20 | let getState, setState, reducerState, _store 21 | export const storeIsDefinedCallback = (store, stateAccessors) => { 22 | ({ getState, setState, reducerState } = stateAccessors(store, reducerKey, initialState)) 23 | _store = store 24 | } 25 | 26 | export const selectors = { 27 | getCounter1: state => 28 | getRState(state, counter1Key), 29 | getCounter2: state => 30 | reducerState[counter2Key], 31 | getCounter3: () => 32 | getState(counter3Key) 33 | } 34 | 35 | export const serviceFunctions = { 36 | onConstructor: store => { 37 | if (_store === undefined) { 38 | throw new Error('store is not defined.') 39 | } 40 | consoleSuccess('Verify store is defined.') 41 | if (typeof setState !== 'function') { 42 | throw new Error('setState is not defined.') 43 | } 44 | consoleSuccess('Verify setState is defined.') 45 | if (typeof getState !== 'function') { 46 | throw new Error('getState is not defined.') 47 | } 48 | consoleSuccess('Verify getState is defined.') 49 | if (reducerState === undefined) { 50 | throw new Error('reducerState is not defined.') 51 | } 52 | consoleSuccess('Verify reducerState is defined.') 53 | }, 54 | increment1: store => 55 | setRState(store, { [counter1Key]: store.getRState(reducerKey)[counter1Key] + 1 }, 'increment1'), 56 | increment2: () => 57 | (reducerState[counter2Key]++), 58 | increment3: () => 59 | setState({ [counter3Key]: getState()[counter3Key] + 1 }, 'increment3') 60 | } 61 | -------------------------------------------------------------------------------- /test/Counter/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connectWithStore } from '../../test/test' 3 | 4 | const testId = 'testId' 5 | 6 | // This tests that refs work properly. 7 | class AddItem extends React.Component { 8 | render () { 9 | return ( 10 |
11 | (this.textInput = c)} 13 | type='text' 14 | id={testId} 15 | defaultValue={20} 16 | /> 17 |
18 | ) 19 | } 20 | } 21 | 22 | const AddItemTest = connectWithStore({ uiComponent: AddItem, isDynamicReducer: false, reduxOptions: { withRef: true } }) 23 | 24 | export default class SimpleReduxJavascript extends React.Component { 25 | componentDidMount () { 26 | const element = this._addItem.getWrappedInstance().textInput 27 | if (typeof element.focus !== 'function') { 28 | throw new Error('Ref failed in AddItemTest1') 29 | } 30 | if (element.id !== testId) { 31 | throw new Error('Ref failed in AddItemTest2') 32 | } 33 | if (element.type.toUpperCase() !== 'TEXT') { 34 | throw new Error('Ref failed in AddItemTest3') 35 | } 36 | } 37 | render () { 38 | return ( 39 |
40 |
Counter: {this.props.counter1}
41 | 42 |
Counter: {this.props.counter2}
43 | 44 |
Counter: {this.props.counter3}
45 | 46 | (this._addItem = c)} /> 47 |
48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Counter1/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | export const selectors = modelDefinition.selectors 7 | export const reducerKey = modelDefinition.reducerKey 8 | -------------------------------------------------------------------------------- /test/Counter1/model.js: -------------------------------------------------------------------------------- 1 | export const reducerKey = 'counter.5' 2 | 3 | let hooksCalled = { 4 | componentDidMount: 0, 5 | onConstructor: 0, 6 | onRender: 0, 7 | componentWillUnmount: 0 8 | } 9 | 10 | export const initialState = { 11 | counter: 10 12 | } 13 | 14 | export const selectors = { 15 | counter: state => { 16 | return state[reducerKey].counter 17 | }, 18 | hooksCalled: () => hooksCalled 19 | } 20 | 21 | export const serviceFunctions = { 22 | increment: store => 23 | store.setRState(reducerKey, { counter: store.getRState(reducerKey).counter + 1 }, 'increment'), 24 | componentDidMount: store => { 25 | if (typeof store.setRState === 'function') { 26 | hooksCalled.componentDidMount++ 27 | } 28 | }, 29 | componentWillUnmount: store => { 30 | if (typeof store.setRState === 'function') { 31 | hooksCalled.componentWillUnmount++ 32 | } 33 | }, 34 | onConstructor: store => { 35 | if (typeof store.setRState === 'function') { 36 | hooksCalled.onConstructor++ 37 | } 38 | }, 39 | onRender: store => { 40 | if (typeof store.setRState === 'function') { 41 | hooksCalled.onRender++ 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Counter1/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Counter1 = props => 4 |
5 |
Counter: {props.counter}
6 | 7 |
8 | 9 | export default Counter1 10 | -------------------------------------------------------------------------------- /test/Counter2/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | -------------------------------------------------------------------------------- /test/Counter2/model.js: -------------------------------------------------------------------------------- 1 | import { consoleSuccess } from '../util' 2 | export const reducerKey = 'counter.15' 3 | 4 | export const initialUIState = { 5 | counter: 10 6 | } 7 | 8 | export const initialState = initialUIState 9 | 10 | const listener = (moduleReducerKey, objToMerge, type) => { 11 | if (moduleReducerKey === undefined) { 12 | throw new Error('listener: Expected moduleReducerKey to be defined.') 13 | } 14 | consoleSuccess('Verify moduleReducerKey is defined.') 15 | if (objToMerge === undefined) { 16 | throw new Error('listener: Expected objToMerge to be defined.') 17 | } 18 | consoleSuccess('Verify objToMerge is defined.') 19 | if (type === undefined) { 20 | throw new Error('listener: Expected type to be defined.') 21 | } 22 | consoleSuccess('Verify listener is defined.') 23 | 24 | if (moduleReducerKey === reducerKey) { 25 | if (objToMerge.counter !== 10) { 26 | throw new Error(`Expected objToMerge.counter to be ${10}.`) 27 | } 28 | consoleSuccess(`Verify objToMerge.counter to be ${10}.`) 29 | } 30 | } 31 | 32 | let reducerState 33 | export const storeIsDefinedCallback = (store, stateAccessors) => { 34 | if (store.addListener === undefined) { 35 | throw new Error('Expected store.addListener to be defined.') 36 | } 37 | ({ reducerState } = stateAccessors(store, reducerKey, initialState)) 38 | store.addListener(listener) 39 | consoleSuccess('Verify store.listener is defined.') 40 | } 41 | 42 | export const serviceFunctions = { 43 | increment: arg => { 44 | let val = reducerState.counter 45 | if (arg !== 10) { 46 | throw new Error('Expected an argument of 10, tests noStoreParameterOnServiceFunctions.') 47 | } 48 | consoleSuccess('Verify an argument of 10, tests noStoreParameterOnServiceFunctions.') 49 | reducerState.counter += arg 50 | if (reducerState.counter !== 10 + val) { 51 | throw new Error(`Expected reducerState.counter to be ${10 + val}.`) 52 | } 53 | consoleSuccess(`Verify reducerState.counter is ${10 + val}.`) 54 | }, 55 | increment2: () => { 56 | reducerState.counter++ 57 | } 58 | } 59 | 60 | export const noStoreParameterOnServiceFunctions = true 61 | -------------------------------------------------------------------------------- /test/Counter2/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter}
6 | 7 |
8 | -------------------------------------------------------------------------------- /test/Counter3/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | -------------------------------------------------------------------------------- /test/Counter3/model.js: -------------------------------------------------------------------------------- 1 | import { consoleSuccess } from '../util' 2 | import { buildSelectorsFromUIState } from '../../test/test' 3 | export const reducerKey = 'counter.25' 4 | 5 | const initialUIState = { 6 | counter: 10 7 | } 8 | 9 | export const initialState = initialUIState 10 | 11 | const selectors = buildSelectorsFromUIState(reducerKey, initialUIState) 12 | 13 | // Test selectorList 14 | export const selectorList = [ 15 | { reducerKey, selectors } 16 | ] 17 | 18 | let reducerState 19 | export const storeIsDefinedCallback = (store, stateAccessors) => { 20 | ({ reducerState } = stateAccessors(store, reducerKey, initialState)) 21 | } 22 | 23 | export const serviceFunctions = { 24 | increment: () => { 25 | let val = reducerState.counter 26 | reducerState.counter += 10 27 | if (reducerState.counter !== val + 10) { 28 | throw new Error(`Expected reducerState.counter to be ${val + 10}.`) 29 | } 30 | consoleSuccess(`Verify reducerState.counter is ${val + 10}.`) 31 | }, 32 | increment2: () => { 33 | reducerState.counter++ 34 | } 35 | } 36 | 37 | export const serviceFunctionList = [ 38 | { serviceFunctions } 39 | ] 40 | -------------------------------------------------------------------------------- /test/Counter3/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter}
6 | 7 |
8 | -------------------------------------------------------------------------------- /test/Counter4/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | -------------------------------------------------------------------------------- /test/Counter4/model.js: -------------------------------------------------------------------------------- 1 | import { consoleSuccess } from '../util' 2 | export const reducerKey = 'counter.35' 3 | 4 | const initialUIState = { 5 | counter: 10 6 | } 7 | 8 | export const initialState = initialUIState 9 | 10 | let reducerState 11 | export const storeIsDefinedCallback = (store, stateAccessors) => { 12 | ({ reducerState } = stateAccessors(store, reducerKey, initialState)) 13 | } 14 | 15 | // This module tests a manually defined mapStateToProps. 16 | export const mapStateToProps = state => { 17 | return ({ 18 | counter: state[reducerKey].counter 19 | }) 20 | } 21 | 22 | // This implements the MSTP caching for a manually defined mapStateToProps. 23 | export const mapStateToPropsReducerKeys = [ 24 | reducerKey 25 | ] 26 | 27 | export const serviceFunctions = { 28 | increment: () => { 29 | let val = reducerState.counter 30 | reducerState.counter += 10 31 | if (reducerState.counter !== val + 10) { 32 | throw new Error(`Expected reducerState.counter to be ${val + 10}.`) 33 | } 34 | consoleSuccess(`Verify reducerState.counter is ${val + 10}.`) 35 | }, 36 | increment2: () => { 37 | reducerState.counter++ 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Counter4/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter}
6 | 7 |
8 | -------------------------------------------------------------------------------- /test/Counter5/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | -------------------------------------------------------------------------------- /test/Counter5/model.js: -------------------------------------------------------------------------------- 1 | export const reducerKey = 'counter.55' 2 | 3 | export const initialUIState = { 4 | counter: 10 5 | } 6 | 7 | export const initialState = initialUIState 8 | 9 | let reducerState 10 | export const storeIsDefinedCallback = (store, stateAccessors) => 11 | ({ reducerState } = stateAccessors(store, reducerKey, initialState)) 12 | 13 | export const serviceFunctions = { 14 | increment: () => { 15 | reducerState.counter++ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/Counter5/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter}
6 | 7 |
8 | -------------------------------------------------------------------------------- /test/Counter6/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | -------------------------------------------------------------------------------- /test/Counter6/model.js: -------------------------------------------------------------------------------- 1 | export const reducerKey = 'counter.66' 2 | 3 | export const initialUIState = { 4 | counter: 10 5 | } 6 | 7 | export const initialState = { 8 | initialUIState, 9 | cache: null 10 | } 11 | 12 | let reducerState 13 | export const storeIsDefinedCallback = (store, stateAccessors) => 14 | ({ reducerState } = stateAccessors(store, reducerKey, initialState)) 15 | 16 | export const serviceFunctions = { 17 | increment: () => { 18 | reducerState.counter++ 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Counter6/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter}
6 | 7 |
8 | -------------------------------------------------------------------------------- /test/Counter7/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | export const selectors = modelDefinition.selectors 7 | export const reducerKey = modelDefinition.reducerKey 8 | -------------------------------------------------------------------------------- /test/Counter7/model.js: -------------------------------------------------------------------------------- 1 | export const reducerKey = 'counter.77' 2 | 3 | export const initialState = { 4 | counter7: 0 5 | } 6 | 7 | export const selectors = { 8 | counter7: state => { 9 | return state[reducerKey].counter7 10 | } 11 | } 12 | 13 | let reducerState 14 | export const storeIsDefinedCallback = (store, stateAccessors) => 15 | ({ reducerState } = stateAccessors(store, reducerKey, initialState)) 16 | 17 | export const serviceFunctions = { 18 | increment: () => { 19 | reducerState.counter7++ 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Counter7/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter7}
6 | 7 |
8 | -------------------------------------------------------------------------------- /test/Counter8/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | -------------------------------------------------------------------------------- /test/Counter8/model.js: -------------------------------------------------------------------------------- 1 | import { reducerKey as reducerKeyCounter1, selectors as selectorsCounter1 } from '../Counter1' 2 | import { reducerKey as reducerKeyCounter7, selectors as selectorsCounter7 } from '../Counter7' 3 | 4 | export const selectorList = [ 5 | { reducerKey: reducerKeyCounter1, selectors: selectorsCounter1, keylist: ['counter'] }, 6 | { reducerKey: reducerKeyCounter7, selectors: selectorsCounter7 } 7 | ] 8 | -------------------------------------------------------------------------------- /test/Counter8/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 |
Counter: {props.counter}
6 |
Counter: {props.counter7}
7 |
8 | -------------------------------------------------------------------------------- /test/WrapCounter1/index.js: -------------------------------------------------------------------------------- 1 | import { connectWithStore, generalReducer } from '../../test/test' 2 | import uiComponent from './view' 3 | import * as modelDefinition from './model' 4 | 5 | export default connectWithStore({ uiComponent, ...modelDefinition }) 6 | export const reducerKey = modelDefinition.reducerKey 7 | export const reducer = generalReducer(modelDefinition.reducerKey, modelDefinition.initialState) 8 | export const exportedServiceFunctions = modelDefinition.exportedServiceFunctions 9 | -------------------------------------------------------------------------------- /test/WrapCounter1/model.js: -------------------------------------------------------------------------------- 1 | export const reducerKey = 'counter.10' 2 | 3 | let simplerStore 4 | export const storeIsDefinedCallback = store => { 5 | simplerStore = store 6 | } 7 | 8 | export const initialState = { 9 | remove: true 10 | } 11 | 12 | export const selectors = { 13 | remove: state => state[reducerKey].remove 14 | } 15 | 16 | export const exportedServiceFunctions = { 17 | remove: () => 18 | simplerStore.setRState(reducerKey, { remove: true }) 19 | } 20 | -------------------------------------------------------------------------------- /test/WrapCounter1/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Counter1 from '../Counter1' 3 | 4 | const WrapCounter1 = props => { 5 | if (props.remove) { 6 | return
7 | } 8 | return 9 | } 10 | 11 | export default WrapCounter1 12 | -------------------------------------------------------------------------------- /test/mountapp.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | import { configure, mount } from 'enzyme' 5 | import App from './App' 6 | 7 | configure({ adapter: new Adapter() }) 8 | 9 | // Mount the App 10 | const appMount = mount() 11 | 12 | export default appMount 13 | -------------------------------------------------------------------------------- /test/projectsetup.js: -------------------------------------------------------------------------------- 1 | import appMount from './mountapp' 2 | 3 | const waitTime = 10000 4 | let intervalID 5 | 6 | // 7 | // React rendering is asynchronous. Components must be validated asynchronously. 8 | // 9 | const handleReactAsync = (done, startTime, waitTime, callbackCheck) => { 10 | // The callback checks that the conditions for success have been met 11 | if (callbackCheck()) { 12 | clearInterval(intervalID) 13 | done() 14 | // Timeout means failure. 15 | } else if (new Date() - startTime > waitTime) { 16 | clearInterval(intervalID) 17 | done(new Error('Timeout')) 18 | } 19 | update() 20 | } 21 | 22 | export const handleReactAsyncStart = (done, waitTime, callbackCheck) => { 23 | intervalID = setInterval(handleReactAsync, 10, done, new Date(), waitTime, callbackCheck) 24 | } 25 | 26 | const findNode = selector => { 27 | if (typeof selector === 'function') { 28 | return appMount.findWhere(selector) 29 | } 30 | return appMount.find(selector) 31 | } 32 | 33 | export const findNodeFunction = (type, id) => 34 | n => n.type() === type && n.props().id === id 35 | 36 | export const nodeExists = selector => findNode(selector).first().exists() 37 | export const nodeString = selector => { 38 | const node = findNode(selector).first() 39 | if (node.length === 0) { 40 | return '' 41 | } 42 | return node.text() 43 | } 44 | export const nodeValue = selector => findNode(selector).props().value 45 | export const simulateClick = selector => findNode(selector).first().simulate('click') 46 | export const simulateInput = (selector, value) => findNode(selector).first().simulate('change', { target: { value } }) 47 | export const update = () => appMount.update() 48 | export { appMount } 49 | 50 | export const testCauseAndEffectWithExists = (causeSelector, effectSelector, done) => { 51 | simulateClick(causeSelector) 52 | handleReactAsyncStart(done, waitTime, () => 53 | nodeExists(effectSelector) 54 | ) 55 | } 56 | 57 | export const testCauseAndEffectWithNotExists = (causeSelector, effectSelector, done) => { 58 | simulateClick(causeSelector) 59 | handleReactAsyncStart(done, waitTime, () => 60 | !nodeExists(effectSelector) 61 | ) 62 | } 63 | 64 | export const testCauseAndEffectWithHtmlString = (causeSelector, effectSelector, expectedHtmlString, done) => { 65 | simulateClick(causeSelector) 66 | handleReactAsyncStart(done, waitTime, () => 67 | nodeString(effectSelector) === expectedHtmlString 68 | ) 69 | } 70 | 71 | export const testCauseAndEffectWithTextField = (causeSelector, inputValue, expectedValue, done) => { 72 | simulateInput(causeSelector, inputValue) 73 | handleReactAsyncStart(done, waitTime, () => 74 | nodeValue(causeSelector) === expectedValue 75 | ) 76 | } 77 | 78 | export const testHtmlString = (effectSelector, expectedHtmlString, done) => { 79 | handleReactAsyncStart(done, waitTime, () => 80 | nodeString(effectSelector) === expectedHtmlString 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /test/reducers.js: -------------------------------------------------------------------------------- 1 | import { reducerKey as wrapCounter1ReducerKey, reducer as wrapCounter1Reducer } from './WrapCounter1' 2 | 3 | export default { 4 | [wrapCounter1ReducerKey]: wrapCounter1Reducer 5 | } 6 | -------------------------------------------------------------------------------- /test/reduxstore.js: -------------------------------------------------------------------------------- 1 | import reducersObject from './reducers' 2 | import { createStore, combineReducers } from 'redux' 3 | import { registerSimplerRedux, reducersPreloadedState } from './test' 4 | 5 | export let mstpCacheHits = 0 6 | export let mstpCalcs = 0 7 | 8 | // These below have been set to an initialization in the modules that would cause failure. 9 | // So, the preloadedState sets them to the correct initialization proving that 10 | // the preloadedState works properly. 11 | const preloadedState = { 12 | 'counter.15': { counter: 0 }, 13 | 'counter.4': { counter1: 0, counter2: 0, counter3: 0 }, 14 | 'counter.5': { counter: 0 }, 15 | 'counter.10': { remove: false } 16 | } 17 | 18 | const mapStateToPropsCalc = obj => { 19 | mstpCalcs++ 20 | } 21 | 22 | const mapStateToPropsCache = obj => { 23 | mstpCacheHits++ 24 | } 25 | 26 | const options = { 27 | mapStateToPropsCalc, 28 | mapStateToPropsCache 29 | } 30 | 31 | const store = registerSimplerRedux( 32 | createStore( 33 | combineReducers(reducersObject), 34 | reducersPreloadedState(reducersObject, preloadedState) // Removes dynamically loaded reducer keys. 35 | ), 36 | reducersObject, // Allows dynamic reducer loading. 37 | preloadedState, // Tests preloaded state on the dynamic loaded reducers. 38 | options // Options 39 | ) 40 | 41 | export default store 42 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | const { JSDOM } = require('jsdom') 2 | 3 | const jsdom = new JSDOM('', { 4 | url: 'http://localhost' 5 | }) 6 | 7 | const { window } = jsdom 8 | global.window = window 9 | global.document = window.document 10 | 11 | // 12 | // Testing for react 16. 13 | // 14 | global.requestAnimationFrame = function (callback) { 15 | setTimeout(callback, 0) 16 | } 17 | 18 | Object.keys(global.window).forEach(property => { 19 | if (typeof global[property] === 'undefined') { 20 | global[property] = global.window[property] 21 | } 22 | }) 23 | 24 | global.navigator = { 25 | userAgent: 'node.js' 26 | } 27 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | // 3 | // Handle mocha testing. 4 | // 5 | import { createStore as createReduxStore, combineReducers } from 'redux' 6 | import { consoleTitle } from './util' 7 | const assert = require('assert') 8 | const isEqual = require('lodash/isEqual') 9 | 10 | let simplerRedux 11 | // Use the source when testing with the debugger. This is "Debug Mocha Tests" entry. 12 | if (process.env.NODE_ENV === 'debugTesting') { 13 | consoleTitle('Testing ../src/index.js.') 14 | simplerRedux = require('../src/index.js') 15 | // Test the lib version. "Run Mocha Tests" entry. 16 | } else { 17 | consoleTitle('Testing ../lib/simpler-redux.js.') 18 | simplerRedux = require('../lib/simpler-redux.js') 19 | } 20 | 21 | export const { 22 | registerSimplerRedux, 23 | generalReducer, 24 | connectWithStore, 25 | allServiceFunctionsToProps, 26 | allServiceFunctionsToPropsWithStore, 27 | allStateToProps, 28 | getStateFunction, 29 | stateAccessors, 30 | setStateFunction, 31 | reducersPreloadedState, 32 | buildSelectorsFromUIState, 33 | createModuleData, 34 | createStore 35 | } = simplerRedux 36 | 37 | let reducersObject = { 38 | } 39 | 40 | let initialState = { 41 | boolVal: false, 42 | intVal: 0, 43 | stringVal: '', 44 | objVal: {}, 45 | arrVal: [] 46 | } 47 | 48 | const numModules = 1000 49 | const baseModuleName = 'module' 50 | const numberStateTransitionsPerKey = 1000 51 | 52 | // Total testing of 1,000,000 state transitions on 1000 reducer keys. 53 | consoleTitle(`Total number of redux reducer keys being tested: ${numModules}.`) 54 | consoleTitle(`Total number of redux state transitions per key being tested: ${numberStateTransitionsPerKey}.`) 55 | consoleTitle(`Total number of redux state transitions being tested: ${numModules * numberStateTransitionsPerKey}.`) 56 | 57 | for (let i = 0; i < numModules; ++i) { 58 | let moduleName = `${baseModuleName}${i}` 59 | reducersObject[moduleName] = generalReducer(moduleName, initialState) 60 | } 61 | 62 | const reduxStore = registerSimplerRedux( 63 | createReduxStore( 64 | combineReducers(reducersObject) 65 | ), 66 | reducersObject 67 | ) 68 | 69 | describe('Verify setup.', function () { 70 | it(`The reduxStore is valid.`, function () { 71 | assert(reduxStore !== 'undefined') 72 | }) 73 | it(`The redux state should contain ${numModules} keys.`, function () { 74 | const st = reduxStore.getState() 75 | assert(Object.keys(st).length === numModules + 1) 76 | }) 77 | it(`The redux state should match the ${numModules} defined keys.`, function () { 78 | const st = reduxStore.getState() 79 | for (let i = 0; i < numModules; ++i) { 80 | let moduleName = `${baseModuleName}${i}` 81 | assert(isEqual(st[moduleName], initialState)) 82 | } 83 | }) 84 | }) 85 | 86 | describe('Test initial getState.', function () { 87 | it(`Each getState on the moduleName should match initialState.`, function () { 88 | for (let i = 0; i < numModules; ++i) { 89 | let moduleName = `${baseModuleName}${i}` 90 | assert(isEqual(reduxStore.getRState(moduleName), initialState)) 91 | } 92 | }) 93 | }) 94 | 95 | describe(`Test setState/getState with the number of state transitions on each key=${numberStateTransitionsPerKey}.`, function () { 96 | for (let i = 0; i < numberStateTransitionsPerKey; ++i) { 97 | initialState.boolVal = i % 2 === 0 98 | initialState.intVal = i 99 | initialState.stringVal = `test${i}` 100 | initialState.objVal = { a: `test${i}` } 101 | initialState.intVal = [`test${i}`] 102 | for (let i = 0; i < numModules; ++i) { 103 | let moduleName = `${baseModuleName}${i}` 104 | reduxStore.setRState(moduleName, initialState) 105 | } 106 | it(`Verify setState performed a state transition (pointer change) on each key state transition=${i}.`, function () { 107 | for (let i = 0; i < numModules; ++i) { 108 | let moduleName = `${baseModuleName}${i}` 109 | assert(reduxStore.getRState(moduleName) !== initialState) 110 | } 111 | }) 112 | it(`Verify getState is equal to the new state transition for each key state transition=${i}.`, function () { 113 | for (let i = 0; i < numModules; ++i) { 114 | let moduleName = `${baseModuleName}${i}` 115 | assert(isEqual(reduxStore.getRState(moduleName), initialState)) 116 | } 117 | }) 118 | } 119 | }) 120 | 121 | const moduleDataReducer = 'moduleDataReducer' 122 | const moduleData = { 123 | counter: 0 124 | } 125 | describe(`Test createModuleData`, function () { 126 | let dataObj = createModuleData(reduxStore, moduleDataReducer, moduleData) 127 | it(`counter should be 0.`, function () { 128 | assert(dataObj.getState().counter === 0) 129 | }) 130 | it(`counter should be 1.`, function () { 131 | dataObj.setState({counter: 1}) 132 | assert(dataObj.getState().counter === 1) 133 | }) 134 | it(`counter should be 2.`, function () { 135 | dataObj.reducerState.counter++ 136 | assert(dataObj.reducerState.counter === 2) 137 | }) 138 | }) 139 | 140 | describe('Test one reducer.', function () { 141 | let reduxStoreOneReducer = createStore() 142 | const testOneReducerKey = 'testOneReducerKey' 143 | 144 | reduxStoreOneReducer.addReducer(testOneReducerKey, initialState) 145 | it(`The testOneReducerKey is valid.`, function () { 146 | assert(isEqual(reduxStoreOneReducer.getRState(testOneReducerKey), initialState)) 147 | }) 148 | 149 | let preloadedState = {} 150 | preloadedState[testOneReducerKey] = { 151 | boolVal: true, 152 | intVal: 1, 153 | stringVal: 'foo', 154 | objVal: {key: 'bar'}, 155 | arrVal: [1] 156 | } 157 | 158 | let reduxStoreOneReducer2 = createStore(preloadedState) 159 | reduxStoreOneReducer2.addReducer(testOneReducerKey, initialState) 160 | it(`The pre-loaded state should be valid.`, function () { 161 | assert(isEqual(reduxStoreOneReducer2.getRState(testOneReducerKey), { ...preloadedState[testOneReducerKey] })) 162 | }) 163 | 164 | it(`Verify the separation of the two stores.`, function () { 165 | for (let i = 0; i < numberStateTransitionsPerKey; ++i) { 166 | reduxStoreOneReducer.setRState(testOneReducerKey, { intVal: i }) 167 | assert(reduxStoreOneReducer.getRState(testOneReducerKey)['intVal'] === i) 168 | assert(reduxStoreOneReducer.getRState(testOneReducerKey)['intVal'] !== 169 | reduxStoreOneReducer2.getRState(testOneReducerKey)['intVal'] 170 | ) 171 | reduxStoreOneReducer2.setRState(testOneReducerKey, { intVal: i }) 172 | assert(reduxStoreOneReducer2.getRState(testOneReducerKey)['intVal'] === i) 173 | } 174 | }) 175 | }) 176 | -------------------------------------------------------------------------------- /test/testApp.js: -------------------------------------------------------------------------------- 1 | import { testHtmlString, testCauseAndEffectWithHtmlString, handleReactAsyncStart } from './projectsetup' 2 | import { selectors } from './Counter1' 3 | import { exportedServiceFunctions } from './WrapCounter1' 4 | import { serviceFunctions as counter1ServiceFunctions, reducerKey as counter1ReducerKey } from './Counter1/model' 5 | import { serviceFunctions as counter5ServiceFunctions, reducerKey as counter5ReducerKey } from './Counter5/model' 6 | import { serviceFunctions as counter3ServiceFunctions, reducerKey as counter3ReducerKey } from './Counter3/model' 7 | import { serviceFunctions as counter4ServiceFunctions, reducerKey as counter4ReducerKey } from './Counter4/model' 8 | import { serviceFunctions as counter6ServiceFunctions, reducerKey as counter6ReducerKey } from './Counter6/model' 9 | import { serviceFunctions as counter7ServiceFunctions, reducerKey as counter7ReducerKey } from './Counter7/model' 10 | import store, { mstpCacheHits, mstpCalcs } from './reduxstore' 11 | import assert from 'assert' 12 | 13 | const waitForAnswer = (done, callbackCheck) => { 14 | handleReactAsyncStart(done, 10000, callbackCheck) 15 | } 16 | 17 | let i = 0 18 | describe('View CounterForm', function () { 19 | this.slow(6000) 20 | it(`increment counter4 - validated.`, function (done) { 21 | testCauseAndEffectWithHtmlString('#increment4', '#counter4', `Counter: ${i + 1}`, done) 22 | }) 23 | it(`increment counter4 - validated 2.`, function (done) { 24 | testCauseAndEffectWithHtmlString('#increment4', '#counter4', `Counter: ${2}`, done) 25 | }) 26 | it(`increment counter1 - validated.`, function (done) { 27 | testCauseAndEffectWithHtmlString('#increment1', '#counter1', `Counter: ${i + 1}`, done) 28 | }) 29 | it(`increment counter2 - validated.`, function (done) { 30 | testCauseAndEffectWithHtmlString('#increment2', '#counter2', `Counter: ${i + 1}`, done) 31 | }) 32 | it(`increment counter3 - validated.`, function (done) { 33 | testCauseAndEffectWithHtmlString('#increment3', '#counter3', `Counter: ${i + 1}`, done) 34 | }) 35 | it(`increment counter15 - validated.`, function (done) { 36 | testCauseAndEffectWithHtmlString('#increment15', '#counter15', `Counter: 10`, done) 37 | }) 38 | it(`increment counter25 - validated.`, function (done) { 39 | testCauseAndEffectWithHtmlString('#increment25', '#counter25', `Counter: 20`, done) 40 | }) 41 | it(`increment counter35 - validated.`, function (done) { 42 | testCauseAndEffectWithHtmlString('#increment35', '#counter35', `Counter: 20`, done) 43 | }) 44 | }) 45 | 46 | let saveMstpCacheCalcs 47 | let saveMstpCacheHits 48 | describe('Test MSTP caching', function () { 49 | it(`selectors caching validated 1.`, function (done) { 50 | store.setRState(counter1ReducerKey, { counter: 0 }) 51 | saveMstpCacheCalcs = mstpCalcs 52 | saveMstpCacheHits = mstpCacheHits 53 | counter1ServiceFunctions.increment(store) 54 | testHtmlString('#counter4', 'Counter: 1', done) 55 | }) 56 | it(`selectors caching validated 2.`, function () { 57 | assert(saveMstpCacheCalcs + 2 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 58 | }) 59 | it(`Manually defined mapStateToProps caching validated 1.`, function (done) { 60 | store.setRState(counter4ReducerKey, { counter: 0 }) 61 | saveMstpCacheCalcs = mstpCalcs 62 | saveMstpCacheHits = mstpCacheHits 63 | counter4ServiceFunctions.increment2() 64 | testHtmlString('#counter35', 'Counter: 1', done) 65 | }) 66 | it(`Manually defined mapStateToProps caching validated 2.`, function () { 67 | assert(saveMstpCacheCalcs + 1 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 68 | }) 69 | it(`selectorList caching validated 1.`, function (done) { 70 | store.setRState(counter4ReducerKey, { counter: 0 }) 71 | saveMstpCacheCalcs = mstpCalcs 72 | saveMstpCacheHits = mstpCacheHits 73 | counter4ServiceFunctions.increment2() 74 | testHtmlString('#counter35', 'Counter: 1', done) 75 | }) 76 | it(`selectorList caching validated 2.`, function () { 77 | assert(saveMstpCacheCalcs + 1 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 78 | }) 79 | it(`selectors caching validated 1.`, function (done) { 80 | store.setRState(counter3ReducerKey, { counter: 0 }) 81 | saveMstpCacheCalcs = mstpCalcs 82 | saveMstpCacheHits = mstpCacheHits 83 | counter3ServiceFunctions.increment2() 84 | testHtmlString('#counter25', 'Counter: 1', done) 85 | }) 86 | it(`selectors caching validated 2.`, function () { 87 | assert(saveMstpCacheCalcs + 1 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 88 | }) 89 | it(`fast MapStateToProps validated 1.`, function (done) { 90 | store.setRState(counter5ReducerKey, { counter: 0 }) 91 | saveMstpCacheCalcs = mstpCalcs 92 | saveMstpCacheHits = mstpCacheHits 93 | counter5ServiceFunctions.increment() 94 | testHtmlString('#counter55', 'Counter: 1', done) 95 | }) 96 | it(`fast MapStateToProps caching validated 2.`, function () { 97 | assert(saveMstpCacheCalcs === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 98 | }) 99 | it(`Ui state is a substate of reducer state caching validated 1.`, function (done) { 100 | store.setRState(counter6ReducerKey, { counter: 0 }) 101 | saveMstpCacheCalcs = mstpCalcs 102 | saveMstpCacheHits = mstpCacheHits 103 | counter6ServiceFunctions.increment() 104 | testHtmlString('#counter66', 'Counter: 1', done) 105 | }) 106 | it(`Ui state is a substate of reducer state validated 2.`, function () { 107 | assert(saveMstpCacheCalcs + 1 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 108 | }) 109 | it(`Multi selectorList caching validated 1.`, function (done) { 110 | store.setRState(counter1ReducerKey, { counter: 0 }) 111 | saveMstpCacheCalcs = mstpCalcs 112 | saveMstpCacheHits = mstpCacheHits 113 | counter1ServiceFunctions.increment(store) 114 | testHtmlString('#counter88a', 'Counter: 1', done) 115 | }) 116 | it(`Multi selectorList caching validated 2.`, function () { 117 | assert(saveMstpCacheCalcs + 2 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 118 | }) 119 | it(`Multi selectorList caching validated 3.`, function (done) { 120 | store.setRState(counter7ReducerKey, { counter7: 0 }) 121 | saveMstpCacheCalcs = mstpCalcs 122 | saveMstpCacheHits = mstpCacheHits 123 | counter7ServiceFunctions.increment() 124 | testHtmlString('#counter88b', 'Counter: 1', done) 125 | }) 126 | it(`Multi selectorList caching validated 4.`, function () { 127 | assert(saveMstpCacheCalcs + 2 === mstpCalcs && mstpCacheHits > saveMstpCacheHits) 128 | }) 129 | }) 130 | 131 | describe('Test Lifecyle component', function () { 132 | it(`componentDidMount hook called 1 time.`, function (done) { 133 | waitForAnswer(done, () => selectors.hooksCalled().componentDidMount === 1) 134 | }) 135 | it(`onConstructor hook called 1 time.`, function (done) { 136 | waitForAnswer(done, () => selectors.hooksCalled().onConstructor === 1) 137 | }) 138 | it(`onRender hook called > 10 times.`, function (done) { 139 | waitForAnswer(done, () => selectors.hooksCalled().onRender > 1) 140 | }) 141 | it(`componentWillUnmount hook called 1 time.`, function (done) { 142 | exportedServiceFunctions.remove() 143 | waitForAnswer(done, () => selectors.hooksCalled().componentWillUnmount === 1) 144 | }) 145 | }) 146 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | const failColor = '\x1b[31m' 2 | const successColor = '\x1b[32m' 3 | const normalColor = '\x1b[37m' 4 | const colorReset = '\x1b[0m' 5 | const bright = '\x1b[1m' 6 | 7 | export const consoleSuccess = msg => 8 | console.log(successColor, '√', normalColor, msg) 9 | 10 | export const consoleFail = msg => 11 | console.log(failColor, msg, colorReset) 12 | 13 | export const consoleTitle = msg => 14 | console.log(bright, msg, colorReset) 15 | --------------------------------------------------------------------------------