├── .eslintrc ├── .gitignore ├── ImmutableStoreMixin.js ├── LICENSE ├── README.md ├── StoreMixin.js ├── lib └── localStorage.js └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", // https://github.com/babel/babel-eslint 3 | "plugins": [ 4 | "react" // https://github.com/yannickcr/eslint-plugin-react 5 | ], 6 | "env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments 7 | "browser": true, // browser global variables 8 | "node": true, // Node.js global variables and Node.js-specific rules 9 | "es6": true 10 | }, 11 | "ecmaFeatures": { 12 | "arrowFunctions": true, 13 | "blockBindings": true, 14 | "classes": true, 15 | "defaultParams": true, 16 | "destructuring": true, 17 | "forOf": true, 18 | "generators": false, 19 | "modules": true, 20 | "objectLiteralComputedProperties": true, 21 | "objectLiteralDuplicateProperties": false, 22 | "objectLiteralShorthandMethods": true, 23 | "objectLiteralShorthandProperties": true, 24 | "spread": true, 25 | "superInFunctions": true, 26 | "templateStrings": true, 27 | "jsx": true 28 | }, 29 | "globals": { 30 | "document": true, 31 | "window": true, 32 | "_": true, 33 | 34 | "describe": true, 35 | "it": true, 36 | "expect": true 37 | }, 38 | "rules": { 39 | 40 | "no-undef": 2, 41 | 42 | 43 | /** 44 | * Strict mode 45 | */ 46 | // babel inserts "use strict"; for us 47 | "strict": [2, "never"], // http://eslint.org/docs/rules/strict 48 | 49 | /** 50 | * ES6 51 | */ 52 | "no-var": 2, // http://eslint.org/docs/rules/no-var 53 | "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const 54 | 55 | /** 56 | * Variables 57 | */ 58 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 59 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 60 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 61 | "vars": "local", 62 | "args": "after-used" 63 | }], 64 | "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define 65 | 66 | /** 67 | * Possible errors 68 | */ 69 | "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle 70 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 71 | "no-console": 1, // http://eslint.org/docs/rules/no-console 72 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 73 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 74 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 75 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 76 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 77 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 78 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 79 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 80 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 81 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 82 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 83 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 84 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 85 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 86 | "no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys 87 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 88 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 89 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 90 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var 91 | 92 | /** 93 | * Best practices 94 | */ 95 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 96 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 97 | "default-case": 2, // http://eslint.org/docs/rules/default-case 98 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 99 | "allowKeywords": true 100 | }], 101 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 102 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in 103 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 104 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 105 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 106 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 107 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 108 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 109 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 110 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 111 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 112 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 113 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 114 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 115 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 116 | "no-new": 2, // http://eslint.org/docs/rules/no-new 117 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 118 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 119 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 120 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 121 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign 122 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 123 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 124 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 125 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 126 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 127 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 128 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 129 | "no-with": 2, // http://eslint.org/docs/rules/no-with 130 | "radix": 2, // http://eslint.org/docs/rules/radix 131 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top 132 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 133 | "yoda": 2, // http://eslint.org/docs/rules/yoda 134 | 135 | /** 136 | * Style 137 | */ 138 | "indent": 2, // http://eslint.org/docs/rules/indent 139 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 140 | "stroustrup", { 141 | "allowSingleLine": true 142 | }], 143 | "quotes": [ 144 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 145 | ], 146 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 147 | "properties": "never" 148 | }], 149 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 150 | "before": false, 151 | "after": true 152 | }], 153 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 154 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 155 | "func-names": 1, // http://eslint.org/docs/rules/func-names 156 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 157 | "beforeColon": false, 158 | "afterColon": true 159 | }], 160 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap 161 | "newIsCap": true 162 | }], 163 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 164 | "max": 2 165 | }], 166 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 167 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 168 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 169 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 170 | "no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func 171 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 172 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 173 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 174 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 175 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 176 | "before": false, 177 | "after": true 178 | }], 179 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords 180 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 181 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 182 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 183 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 184 | "spaced-line-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment 185 | 186 | /** 187 | * JSX style 188 | */ 189 | "react/display-name": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md 190 | "react/jsx-boolean-value": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 191 | "react/jsx-quotes": [2, "double"], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-quotes.md 192 | "react/jsx-no-undef": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md 193 | "react/jsx-sort-props": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 194 | "react/jsx-sort-prop-types": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-prop-types.md 195 | "react/jsx-uses-react": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md 196 | "react/jsx-uses-vars": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md 197 | "react/no-did-mount-set-state": [2, "allow-in-func"], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md 198 | "react/no-did-update-set-state": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md 199 | "react/no-multi-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md 200 | "react/no-unknown-property": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md 201 | "react/prop-types": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md 202 | "react/react-in-jsx-scope": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md 203 | "react/self-closing-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md 204 | "react/wrap-multilines": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md 205 | "react/sort-comp": [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md 206 | "order": [ 207 | "displayName", 208 | "propTypes", 209 | "contextTypes", 210 | "childContextTypes", 211 | "mixins", 212 | "statics", 213 | "defaultProps", 214 | "getDefaultProps", 215 | "getInitialState", 216 | "getChildContext", 217 | "componentWillMount", 218 | "componentDidMount", 219 | "componentWillReceiveProps", 220 | "shouldComponentUpdate", 221 | "componentWillUpdate", 222 | "componentDidUpdate", 223 | "componentWillUnmount", 224 | "/^on.+$/", 225 | "/^get.+$/", 226 | "/^_.+$/", 227 | "render", 228 | "/^render.+$/" 229 | ] 230 | }] 231 | } 232 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /ImmutableStoreMixin.js: -------------------------------------------------------------------------------- 1 | var Immutable = require('immutable'); 2 | var _localStorage = require('./lib/localStorage'); 3 | 4 | var ImmutableStoreMixin = { 5 | state: Immutable.Map(), 6 | init: function() { 7 | // Update state with cached data 8 | var nextState = this._getCachedState(); 9 | 10 | if (nextState) { 11 | this.setState(nextState); 12 | } 13 | }, 14 | setState: function(nextState, option) { 15 | if (!nextState) { return null; } 16 | 17 | var triggerUpdate; 18 | var cb; 19 | 20 | var optionType = typeof option; 21 | 22 | if (optionType === 'boolean') { 23 | triggerUpdate = option; 24 | var _err = new Error('ImmutableStoreMixin.setState:: `triggerUpdate`, will be depracated in future release.'); 25 | console.warn(_err.stack); 26 | } 27 | else { 28 | cb = option; 29 | } 30 | 31 | var _targetState = this.state.merge(nextState); 32 | 33 | if (_targetState !== this.state) { 34 | this.state = _targetState; 35 | 36 | if (triggerUpdate !== false) { 37 | this.trigger(); 38 | this._updateLocalStorage(); 39 | } 40 | 41 | // Trigger callback if it is passed in 42 | if (cb) { 43 | return cb(); 44 | } 45 | } 46 | }, 47 | get: function(key) { 48 | if (this.onFirstRequest && !this._ImmutableStoreMixinRequested) { 49 | this._ImmutableStoreMixinRequested = true; 50 | this.onFirstRequest(); 51 | } 52 | 53 | return this.state.get(key); 54 | }, 55 | _updateLocalStorage: function() { 56 | if (!this.localStorageKey) { 57 | // Don't save state unless we need too 58 | return null; 59 | } 60 | 61 | var _stateString; 62 | 63 | if (this.stateToString) { 64 | // Custom conversion to string 65 | _stateString = this.stateToString(this.state); 66 | 67 | if (!_stateString) { 68 | var err = new Error('`stateToString()` must return a valid JSON string'); 69 | console.warn(err.stack); 70 | return null; 71 | } 72 | } 73 | else { 74 | _stateString = JSON.stringify(this.state.toJS()); 75 | } 76 | 77 | _localStorage.setItem(this.localStorageKey, _stateString); 78 | }, 79 | _getCachedState: function() { 80 | if (!this.localStorageKey) { 81 | // Don't get state unless we need too 82 | return null; 83 | } 84 | 85 | var _stateString = _localStorage.getItem(this.localStorageKey); 86 | 87 | if (!_stateString || _stateString === '') { 88 | return null; 89 | } 90 | 91 | var _cachedState; 92 | 93 | if (this.stateFromString) { 94 | _cachedState = this.stateFromString(_stateString); 95 | 96 | if (!_cachedState) { 97 | var err = new Error('`stateFromString()` must return a Immutable.Map'); 98 | console.warn(err.stack); 99 | return null; 100 | } 101 | } 102 | else { 103 | var _cachedJSON = JSON.parse(_stateString); 104 | _cachedState = Immutable.fromJS(_cachedJSON); 105 | } 106 | 107 | return _cachedState; 108 | } 109 | }; 110 | 111 | module.exports = ImmutableStoreMixin; 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, BigDatr Pty Ltd 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of reflux-immutable nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | reflux-immutable 2 | =================== 3 | 4 | ## Install 5 | 6 | ```sh 7 | npm install reflux-immutable --save 8 | ``` 9 | 10 | ## Useage 11 | 12 | ##### MyStore.js 13 | ```js 14 | 15 | var Reflux = require('reflux'); 16 | var ImmutableStoreMixin = require('reflux-immutable/ImmutableStoreMixin'); 17 | 18 | var SomeActions = require('./SomeActions'); 19 | 20 | var MyStore = Reflux.createStore({ 21 | listenables: SomeActions, 22 | mixins: [ 23 | ImmutableStoreMixin 24 | ], 25 | init: function() { 26 | this.setState({ 27 | message: 'Initial Value' 28 | }); 29 | }, 30 | onMyAction: function() { 31 | this.setState({ 32 | message: 'myAction has been triggered!' 33 | }); 34 | } 35 | }); 36 | 37 | module.exports = MyStore; 38 | 39 | ``` 40 | 41 | ##### MyComponent.js 42 | ```js 43 | 44 | var React = require('react'); 45 | var PureRenderMixin = require('react/addons').addons.PureRenderMixin; 46 | var Reflux = require('reflux'); 47 | var StoreMixin = require('reflux-immutable/StoreMixin'); 48 | 49 | var MyStore = require('./MyStore'); 50 | 51 | var MyComponent = React.createClass({ 52 | displayName: 'MyComponent', 53 | mixins: [ 54 | PureRenderMixin, 55 | StoreMixin, 56 | Reflux.listenTo(MyStore, 'onStoreChange') 57 | ], 58 | getStoreState: function() { 59 | return { 60 | message: MyStore.get('message') 61 | }; 62 | }, 63 | render: function() { 64 | return ( 65 |