├── README.md ├── ng-redux-autocomplete-example ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── package.json ├── server.js ├── src │ ├── actions │ │ ├── autocomplete.js │ │ └── index.js │ ├── components │ │ ├── company-autocomplete.js │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── middleware │ │ ├── http.js │ │ └── index.js │ └── reducers │ │ ├── index.js │ │ └── suggestions.js └── webpack.config.js ├── ng-redux-counter-example ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── package.json ├── server.js ├── src │ ├── actions │ │ ├── counter.js │ │ └── index.js │ ├── components │ │ ├── counter.js │ │ └── index.js │ ├── index.html │ ├── index.js │ └── reducers │ │ ├── counter.js │ │ └── index.js └── webpack.config.js ├── ng-redux-http-example ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── package.json ├── server.js ├── src │ ├── actions │ │ ├── index.js │ │ └── request.js │ ├── components │ │ ├── index.js │ │ └── request.js │ ├── index.html │ ├── index.js │ ├── middleware │ │ ├── http.js │ │ └── index.js │ └── reducers │ │ ├── index.js │ │ └── posts.js └── webpack.config.js ├── ng-redux-multiple-requests ├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── package.json ├── server.js ├── src │ ├── actions │ │ └── promise.js │ ├── api │ │ └── index.js │ ├── components │ │ ├── index.js │ │ └── request.js │ ├── constants │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── middleware │ │ ├── index.js │ │ └── promise.js │ └── reducers │ │ ├── index.js │ │ ├── player.js │ │ └── team.js └── webpack.config.js ├── redux-react-boilerplate ├── .eslintrc ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── index.html ├── package.json ├── src │ ├── actions │ │ ├── counter.js │ │ └── todo.js │ ├── app.js │ ├── components │ │ ├── Counter.js │ │ ├── TodoForm.js │ │ ├── TodoItem.js │ │ └── TodoList.js │ ├── constants │ │ └── index.js │ ├── containers │ │ ├── About.js │ │ ├── App.js │ │ ├── Counter.js │ │ └── Todo.js │ ├── reducers │ │ ├── counter.js │ │ ├── index.js │ │ └── todos.js │ └── store │ │ └── configureStore.js └── webpack.config.js ├── redux-react-routing-auth ├── .eslintrc ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── index.html ├── package.json ├── src │ ├── actions │ │ ├── counter.js │ │ └── login.js │ ├── api │ │ └── index.js │ ├── app.js │ ├── components │ │ ├── counter │ │ │ └── Counter.js │ │ ├── login │ │ │ └── LoginForm.js │ │ ├── navigation │ │ │ ├── Navigator.js │ │ │ └── NavigatorItem.js │ │ └── ui │ │ │ ├── Button.js │ │ │ ├── Content.js │ │ │ ├── FormError.js │ │ │ ├── FormGroup.js │ │ │ ├── InputText.js │ │ │ ├── Label.js │ │ │ ├── Layout.js │ │ │ ├── Text.js │ │ │ └── Title.js │ ├── config │ │ ├── history.js │ │ ├── logger.js │ │ └── store.js │ ├── constants │ │ └── index.js │ ├── containers │ │ ├── Home.js │ │ ├── Login.js │ │ └── Main.js │ ├── middleware │ │ └── promiseMiddleware.js │ ├── reducers │ │ ├── counter.js │ │ ├── index.js │ │ └── session.js │ ├── styles │ │ └── main.scss │ └── utils │ │ └── index.js └── webpack.config.js ├── redux-saga-example ├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── nodemon.json ├── package.json ├── server.js ├── src │ ├── components │ │ ├── Toast.js │ │ └── Toaster.js │ ├── containers │ │ └── App.js │ ├── index.html │ ├── index.js │ ├── middleware │ │ ├── logger.js │ │ └── sagas.js │ ├── reducers │ │ ├── index.js │ │ └── toasts.js │ ├── sagas │ │ └── toastQueue.js │ ├── store │ │ └── configureStore.js │ ├── styles │ │ ├── base.css │ │ └── styles.css │ └── utils │ │ ├── generateRandomToast.js │ │ └── immutableToJS.js └── webpack.config.js └── reselect-example ├── example.js ├── node_modules └── reselect │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib │ └── index.js │ ├── package.json │ └── src │ └── index.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Redux Examples 2 | 3 | This repository contains various examples of Redux applications. 4 | 5 | ## Angular Examples 6 | 7 | ### ng-redux-counter-example 8 | 9 | A very simple example demonstrating a counter. Good introduction to Redux. 10 | 11 | ### ng-redux-autocomplete-example 12 | 13 | Demonstrates a simple autocomplete example using the [Clearbit Autocomplete Company API](https://autocomplete.clearbit.com/v1/companies). 14 | 15 | ### ng-redux-http-example 16 | 17 | Demonstrates a simple http middleware which is responsible for making API calls. 18 | 19 | ### ng-redux-multiple-requests 20 | 21 | Demostrates how to perform multiple requests, pull your code out of Angular (and into vanilla JS), and implement a generic Promise middleware. 22 | 23 | ## React Examples 24 | 25 | ## redux-react-boilerplate 26 | 27 | A simple boilerplate for building Redux applications using React. Uses react router and redux router to demonstrate how to build an application with multiple routes. 28 | 29 | ## redux-react-routing-auth 30 | 31 | Boilerplate for a simple login with redirects if the user is not 32 | authenticated. 33 | 34 | ## Redux 35 | 36 | ## reselect-example 37 | 38 | Shows how to build a selector to select separate parts of your state. Use your stores state combined with a selector to derive new data. 39 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | max_line_length = 80 11 | indent_brace_style = 1TBS 12 | spaces_around_operators = true 13 | quote_type = auto 14 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "globals": { 9 | "angular": true, 10 | "moment": true, 11 | "R": true, 12 | "inject": true, 13 | "Q": true, 14 | "sinon": true, 15 | "getService": true, 16 | "expect": true, 17 | "xit": true, 18 | "dealoc": true, 19 | "require": true 20 | }, 21 | "rules": { 22 | "camelcase": 2, 23 | "curly": 2, 24 | "brace-style": [2, "1tbs"], 25 | "quotes": [2, "single"], 26 | "semi": [2, "always"], 27 | "object-curly-spacing": [2, "never"], 28 | "array-bracket-spacing": [2, "never"], 29 | "computed-property-spacing": [2, "never"], 30 | "space-infix-ops": 2, 31 | "space-after-keywords": [2, "always"], 32 | "dot-notation": 2, 33 | "eqeqeq": [2, "smart"], 34 | "no-use-before-define": 2, 35 | "no-redeclare": 2, 36 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 37 | "no-floating-decimal": 2, 38 | "no-spaced-func": 2, 39 | "no-extra-parens": 1, 40 | "no-underscore-dangle": 0, 41 | "valid-jsdoc": 1, 42 | "space-after-keywords": 2, 43 | "max-len": [2, 120], 44 | "no-use-before-define": [2, "nofunc"], 45 | "no-warning-comments": 0, 46 | "new-cap": 0, 47 | "strict": 0, 48 | "eol-last": 0, 49 | "semi": 2 50 | }, 51 | "ecmaFeatures": { 52 | "modules": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # Runtime data 5 | pids 6 | *.pid 7 | *.seed 8 | 9 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 10 | .grunt 11 | 12 | # Sass cache folder 13 | .sass-cache 14 | 15 | # Users Environment Variables 16 | .lock-wscript 17 | .idea/ 18 | *~ 19 | \#*# 20 | .#* 21 | *.keystore 22 | *.sw* 23 | .DS_Store 24 | ._* 25 | Thumbs.db 26 | .cache 27 | *.sublime-project 28 | *.sublime-workspace 29 | *swp 30 | *swo 31 | *swn 32 | build 33 | dist 34 | temp 35 | .tmp 36 | node_modules 37 | bower_components 38 | jspm_packages 39 | .test 40 | www 41 | lib/ 42 | 43 | keys.md 44 | .env 45 | 46 | # Cordova 47 | platforms/ 48 | plugins/ 49 | 50 | # emacs 51 | .tern-port 52 | # -*- mode: gitignore; -*- 53 | *~ 54 | \#*\# 55 | /.emacs.desktop 56 | /.emacs.desktop.lock 57 | *.elc 58 | auto-save-list 59 | tramp 60 | .\#* 61 | 62 | # Org-mode 63 | .org-id-locations 64 | *_archive 65 | 66 | # flymake-mode 67 | *_flymake.* 68 | 69 | # eshell files 70 | /eshell/history 71 | /eshell/lastdir 72 | 73 | # elpa packages 74 | /elpa/ 75 | 76 | # reftex files 77 | *.rel 78 | 79 | # AUCTeX auto folder 80 | /auto/ 81 | 82 | # cask packages 83 | .cask/ 84 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/README.md: -------------------------------------------------------------------------------- 1 | # Redux Counter Example 2 | 3 | `npm run start` 4 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-redux-counter-example", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node server.js", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "eslint ./src" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "angular": "^1.4.5", 13 | "babel-core": "^5.8.24", 14 | "babel-loader": "^5.3.2", 15 | "ng-redux": "^2.0.2", 16 | "redux": "^2.0.0", 17 | "redux-logger": "^1.0.6", 18 | "redux-thunk": "^0.1.0", 19 | "rx": "^3.1.2" 20 | }, 21 | "devDependencies": { 22 | "extract-text-webpack-plugin": "^0.8.2", 23 | "webpack": "^1.12.1", 24 | "webpack-dev-server": "^1.10.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | stats: { 9 | colors: true 10 | } 11 | }) 12 | .listen(3000, 'localhost', function (err) { 13 | if (err) { 14 | console.log(err); 15 | } 16 | 17 | console.log('http://localhost:3000/src'); 18 | }); 19 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/actions/autocomplete.js: -------------------------------------------------------------------------------- 1 | import {bindActionCreators} from 'redux'; 2 | 3 | export const REQUEST_COMPANY = '@@reduxAction/REQUEST_COMPANY'; 4 | export const REQUEST_COMPANY_SUCCESS = '@@reduxAction/REQUEST_COMPANY_SUCCESS'; 5 | export const REQUEST_COMPANY_ERROR = '@@reduxAction/REQUEST_COMPANY_ERROR'; 6 | 7 | export function queryAutocomplete(query) { 8 | return { 9 | types: [ 10 | REQUEST_COMPANY, 11 | REQUEST_COMPANY_SUCCESS, 12 | REQUEST_COMPANY_ERROR 13 | ], 14 | config: { 15 | method: 'get', 16 | url: `https://autocomplete.clearbit.com/v1/companies/suggest?query=${query}` 17 | }, 18 | meta: { 19 | timestamp: Date.now(), 20 | query: query 21 | } 22 | }; 23 | } 24 | 25 | export default function autoCompleteActions($ngRedux) { 26 | let actionCreator = { 27 | queryAutocomplete 28 | }; 29 | 30 | return bindActionCreators(actionCreator, $ngRedux.dispatch); 31 | } 32 | 33 | autoCompleteActions.$inject = ['$ngRedux']; 34 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import autoCompleteActions from './autocomplete'; 4 | 5 | export default angular 6 | .module('app.actions', []) 7 | .factory('autoCompleteActions', autoCompleteActions) 8 | .name; 9 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/components/company-autocomplete.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | class CompanyAutocomplete { 4 | constructor($scope, $ngRedux, autoCompleteActions) { 5 | 6 | let unsubscribe = $ngRedux.connect(state => ({ 7 | isFetching: state.suggestions.isFetching, 8 | error: state.suggestions.error, 9 | suggestions: state.suggestions.data, 10 | latestFetchAttempt: state.suggestions.latestFetchAttempt 11 | }))(this); 12 | 13 | $scope.$on('$destroy', unsubscribe); 14 | 15 | this.queryAutocomplete = (query) => { 16 | if (query.length <= 0) { 17 | this.suggestions = []; 18 | return; 19 | } 20 | 21 | autoCompleteActions.queryAutocomplete(query); 22 | }; 23 | 24 | this.query = ''; 25 | } 26 | } 27 | 28 | CompanyAutocomplete.$inject = ['$scope', '$ngRedux', 'autoCompleteActions']; 29 | 30 | export default angular 31 | .module('app.autocomplete', []) 32 | .directive('companyAutocomplete', () => ({ 33 | restrict: 'E', 34 | template: ` 35 |
36 |

Type a company name below

37 |
40 | An error occured: {{ autocomplete.error | json }} 41 |
42 |
43 | 50 | 51 | 55 | 56 |
57 | 62 |
63 | `, 64 | controller: 'CompanyAutocomplete', 65 | controllerAs: 'autocomplete' 66 | })) 67 | .controller('CompanyAutocomplete', CompanyAutocomplete) 68 | .name; 69 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/components/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import companyAutocomplete from './company-autocomplete'; 4 | 5 | export default angular 6 | .module('app.components', [ 7 | companyAutocomplete 8 | ]) 9 | .name; 10 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux Example 6 | 7 | 8 |
9 |
10 |
11 |

Company Autocomplete

12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | // redux 4 | import ngRedux from 'ng-redux'; 5 | import rootReducer from './reducers'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import createLogger from 'redux-logger'; 8 | 9 | const logger = createLogger({ 10 | collapsed: true, 11 | level: 'info' 12 | }); 13 | 14 | // angular 15 | import components from './components'; 16 | import actions from './actions'; 17 | import middleware from './middleware'; 18 | 19 | export default angular 20 | .module('app', [ 21 | ngRedux, 22 | components, 23 | actions, 24 | middleware 25 | ]) 26 | .config(($ngReduxProvider) => { 27 | $ngReduxProvider.createStoreWith(rootReducer, [ 28 | logger, 29 | thunkMiddleware, 30 | 'httpMiddleware' 31 | ]); 32 | }) 33 | .name; 34 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/middleware/http.js: -------------------------------------------------------------------------------- 1 | export default function httpMiddleware($http, $timeout) { 2 | function callAPI(config) { 3 | config = angular.extend(config, { 4 | headers: { 5 | 'Content-Type': 'application/json' 6 | } 7 | }); 8 | 9 | return $http(config) 10 | .then(res => res.data); 11 | } 12 | 13 | return ({dispatch, getState}) => next => action => { 14 | const { 15 | types, 16 | config, 17 | shouldCallAPI = () => true, 18 | meta = {} 19 | } = action; 20 | 21 | if (!types) { 22 | // Normal action: pass it on 23 | return next(action); 24 | } 25 | 26 | if ( 27 | !Array.isArray(types) || 28 | types.length !== 3 || 29 | !types.every(type => typeof type === 'string') 30 | ) { 31 | throw new Error('Expected an array of three string types.'); 32 | } 33 | 34 | if (typeof config !== 'object') { 35 | throw new Error('Expected config to be an object. See $http config.'); 36 | } 37 | 38 | if (!shouldCallAPI(getState())) { 39 | return; 40 | } 41 | 42 | const [requestType, successType, failureType] = types; 43 | 44 | dispatch(Object.assign({}, { 45 | type: requestType, 46 | payload: { 47 | meta 48 | } 49 | })); 50 | 51 | return callAPI(config).then( 52 | response => dispatch(Object.assign({}, { 53 | type: successType, 54 | payload: { 55 | response, 56 | meta 57 | } 58 | })), 59 | error => dispatch(Object.assign({}, { 60 | type: failureType, 61 | error: true, 62 | payload: { 63 | error: error.data, 64 | meta 65 | } 66 | })) 67 | ); 68 | } 69 | } 70 | 71 | httpMiddleware.$inject = ['$http', '$timeout']; 72 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import http from './http'; 4 | 5 | export default angular 6 | .module('app.middleware', []) 7 | .factory('httpMiddleware', http) 8 | .name; 9 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import suggestions from './suggestions'; 4 | 5 | const rootReducer = combineReducers({ 6 | suggestions 7 | }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/src/reducers/suggestions.js: -------------------------------------------------------------------------------- 1 | import { 2 | REQUEST_COMPANY, 3 | REQUEST_COMPANY_SUCCESS, 4 | REQUEST_COMPANY_ERROR 5 | } from '../actions/autocomplete'; 6 | 7 | const INITIAL_STATE = { 8 | isFetching: false, 9 | latestFetchAttempt: null, 10 | error: false, 11 | data: null 12 | }; 13 | 14 | export default function suggestions(state = INITIAL_STATE, action) { 15 | const {payload} = action; 16 | 17 | var actions = { 18 | [REQUEST_COMPANY]: (state) => { 19 | return Object.assign({}, state, { 20 | isFetching: true, 21 | error: false, 22 | latestFetchAttempt: payload.meta.timestamp 23 | }); 24 | }, 25 | [REQUEST_COMPANY_SUCCESS]: (state) => { 26 | return Object.assign({}, state, { 27 | isFetching: false, 28 | data: state.latestFetchAttempt === payload.meta.timestamp ? 29 | payload.response : 30 | state.data 31 | }); 32 | }, 33 | [REQUEST_COMPANY_ERROR]: (state) => { 34 | return Object.assign({}, state, { 35 | isFetching: false, 36 | error: payload.error 37 | }); 38 | } 39 | }; 40 | 41 | return actions[action.type] ? 42 | actions[action.type](state) : 43 | state; 44 | } 45 | -------------------------------------------------------------------------------- /ng-redux-autocomplete-example/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | var sassLoaders = [ 6 | 'css-loader', 7 | 'autoprefixer-loader?browsers=last 2 version', 8 | 'sass-loader?includePaths[]=' + path.resolve(__dirname, './src'), 9 | ]; 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | entry: [ 14 | 'webpack-dev-server/client?http://localhost:3000', 15 | 'webpack/hot/dev-server', 16 | './src/index' 17 | ], 18 | output: { 19 | path: path.join(__dirname, 'dist'), 20 | filename: 'bundle.js', 21 | publicPath: '/static/' 22 | }, 23 | plugins: [ 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NoErrorsPlugin() 26 | ], 27 | resolve: { 28 | extensions: ['', '.js'], 29 | alias: { 30 | 'react': path.join(__dirname, '..', '..', 'node_modules', 'react') 31 | } 32 | }, 33 | module: { 34 | loaders: [{ 35 | test: /\.js$/, 36 | loaders: ['babel'], 37 | exclude: /node_modules/ 38 | }, { 39 | // HTML LOADER 40 | // Reference: https://github.com/webpack/raw-loader 41 | // Allow loading html through js 42 | test: /\.html$/, 43 | loader: 'raw' 44 | }] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /ng-redux-counter-example/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | max_line_length = 80 11 | indent_brace_style = 1TBS 12 | spaces_around_operators = true 13 | quote_type = auto 14 | -------------------------------------------------------------------------------- /ng-redux-counter-example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "globals": { 9 | "angular": true, 10 | "moment": true, 11 | "R": true, 12 | "inject": true, 13 | "Q": true, 14 | "sinon": true, 15 | "getService": true, 16 | "expect": true, 17 | "xit": true, 18 | "dealoc": true, 19 | "require": true 20 | }, 21 | "rules": { 22 | "camelcase": 2, 23 | "curly": 2, 24 | "brace-style": [2, "1tbs"], 25 | "quotes": [2, "single"], 26 | "semi": [2, "always"], 27 | "object-curly-spacing": [2, "never"], 28 | "array-bracket-spacing": [2, "never"], 29 | "computed-property-spacing": [2, "never"], 30 | "space-infix-ops": 2, 31 | "space-after-keywords": [2, "always"], 32 | "dot-notation": 2, 33 | "eqeqeq": [2, "smart"], 34 | "no-use-before-define": 2, 35 | "no-redeclare": 2, 36 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 37 | "no-floating-decimal": 2, 38 | "no-spaced-func": 2, 39 | "no-extra-parens": 1, 40 | "no-underscore-dangle": 0, 41 | "valid-jsdoc": 1, 42 | "space-after-keywords": 2, 43 | "max-len": [2, 120], 44 | "no-use-before-define": [2, "nofunc"], 45 | "no-warning-comments": 0, 46 | "new-cap": 0, 47 | "strict": 0, 48 | "eol-last": 0, 49 | "semi": 2 50 | }, 51 | "ecmaFeatures": { 52 | "modules": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ng-redux-counter-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # Runtime data 5 | pids 6 | *.pid 7 | *.seed 8 | 9 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 10 | .grunt 11 | 12 | # Sass cache folder 13 | .sass-cache 14 | 15 | # Users Environment Variables 16 | .lock-wscript 17 | .idea/ 18 | *~ 19 | \#*# 20 | .#* 21 | *.keystore 22 | *.sw* 23 | .DS_Store 24 | ._* 25 | Thumbs.db 26 | .cache 27 | *.sublime-project 28 | *.sublime-workspace 29 | *swp 30 | *swo 31 | *swn 32 | build 33 | dist 34 | temp 35 | .tmp 36 | node_modules 37 | bower_components 38 | jspm_packages 39 | .test 40 | www 41 | lib/ 42 | 43 | keys.md 44 | .env 45 | 46 | # Cordova 47 | platforms/ 48 | plugins/ 49 | 50 | # emacs 51 | .tern-port 52 | # -*- mode: gitignore; -*- 53 | *~ 54 | \#*\# 55 | /.emacs.desktop 56 | /.emacs.desktop.lock 57 | *.elc 58 | auto-save-list 59 | tramp 60 | .\#* 61 | 62 | # Org-mode 63 | .org-id-locations 64 | *_archive 65 | 66 | # flymake-mode 67 | *_flymake.* 68 | 69 | # eshell files 70 | /eshell/history 71 | /eshell/lastdir 72 | 73 | # elpa packages 74 | /elpa/ 75 | 76 | # reftex files 77 | *.rel 78 | 79 | # AUCTeX auto folder 80 | /auto/ 81 | 82 | # cask packages 83 | .cask/ 84 | -------------------------------------------------------------------------------- /ng-redux-counter-example/README.md: -------------------------------------------------------------------------------- 1 | # Redux Counter Example 2 | 3 | `npm run start` 4 | -------------------------------------------------------------------------------- /ng-redux-counter-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-redux-counter-example", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node server.js", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "eslint ./src" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "angular": "^1.4.5", 13 | "babel-core": "^5.8.24", 14 | "babel-loader": "^5.3.2", 15 | "ng-redux": "^2.0.2", 16 | "redux": "^2.0.0", 17 | "redux-thunk": "^0.1.0" 18 | }, 19 | "devDependencies": { 20 | "extract-text-webpack-plugin": "^0.8.2", 21 | "webpack": "^1.12.1", 22 | "webpack-dev-server": "^1.10.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ng-redux-counter-example/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | stats: { 9 | colors: true 10 | } 11 | }) 12 | .listen(3000, 'localhost', function (err) { 13 | if (err) { 14 | console.log(err); 15 | } 16 | 17 | console.log('http://localhost:3000/src'); 18 | }); 19 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/actions/counter.js: -------------------------------------------------------------------------------- 1 | import {bindActionCreators} from 'redux'; 2 | 3 | export const INCREMENT_COUNTER = '@@reduxAction/INCREMENT_COUNTER'; 4 | export const DECREMENT_COUNTER = '@@reduxAction/DECREMENT_COUNTER'; 5 | 6 | export function increment() { 7 | return { 8 | type: INCREMENT_COUNTER 9 | }; 10 | } 11 | 12 | export function decrement() { 13 | return { 14 | type: DECREMENT_COUNTER 15 | }; 16 | } 17 | 18 | export function incrementIfOdd() { 19 | return (dispatch, getState) => { 20 | const {counter} = getState(); 21 | 22 | if (counter % 2 === 0) { 23 | return; 24 | } 25 | 26 | dispatch(increment()); 27 | }; 28 | } 29 | 30 | export function incrementAsync(delay = 1000) { 31 | return dispatch => { 32 | setTimeout(() => { 33 | dispatch(increment()); 34 | }, delay); 35 | }; 36 | } 37 | 38 | export default function counterActions($ngRedux) { 39 | let actionCreator = { 40 | increment, 41 | decrement, 42 | incrementIfOdd, 43 | incrementAsync 44 | }; 45 | 46 | return bindActionCreators(actionCreator, $ngRedux.dispatch); 47 | } 48 | 49 | counterActions.$inject = ['$ngRedux']; 50 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import counterActions from './counter'; 4 | 5 | export default angular 6 | .module('app.actions', []) 7 | .factory('counterActions', counterActions) 8 | .name; 9 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/components/counter.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | class CounterController { 4 | constructor($scope, $ngRedux, counterActions) { 5 | 6 | let unsubscribe = $ngRedux.connect(state => ({counter: state.counter}))(this); 7 | $scope.$on('$destroy', unsubscribe); 8 | 9 | this.increment = counterActions.increment; 10 | this.decrement = counterActions.decrement; 11 | this.incrementAsync = counterActions.incrementAsync; 12 | this.incrementIfOdd = counterActions.incrementIfOdd; 13 | } 14 | } 15 | 16 | CounterController.$inject = ['$scope', '$ngRedux', 'counterActions']; 17 | 18 | export default angular 19 | .module('app.counter', []) 20 | .directive('counter', () => ({ 21 | restrict: 'E', 22 | template: ` 23 |
24 |

{{ counter.counter }}

25 | 26 | 48 |
49 | `, 50 | controller: 'CounterController', 51 | controllerAs: 'counter' 52 | })) 53 | .controller('CounterController', CounterController) 54 | .name; 55 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/components/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import counter from './counter'; 4 | 5 | export default angular 6 | .module('app.components', [ 7 | counter 8 | ]) 9 | .name; 10 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux Example 6 | 7 | 8 |
9 |
10 |
11 |

Counter Example

12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | // redux 4 | import ngRedux from 'ng-redux'; 5 | import rootReducer from './reducers'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | 8 | // angular 9 | import components from './components'; 10 | import actions from './actions'; 11 | 12 | export default angular 13 | .module('app', [ 14 | ngRedux, 15 | components, 16 | actions 17 | ]) 18 | .config(($ngReduxProvider) => { 19 | $ngReduxProvider.createStoreWith(rootReducer, [thunkMiddleware]); 20 | }) 21 | .name; 22 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import {INCREMENT_COUNTER, DECREMENT_COUNTER} from '../actions/counter'; 2 | 3 | export default function counter(state = 0, action) { 4 | var actions = { 5 | [INCREMENT_COUNTER]: (state) => state + 1, 6 | [DECREMENT_COUNTER]: (state) => state - 1 7 | }; 8 | 9 | return actions[action.type] ? 10 | actions[action.type](state) : 11 | state; 12 | } 13 | -------------------------------------------------------------------------------- /ng-redux-counter-example/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import counter from './counter'; 4 | 5 | const rootReducer = combineReducers({ 6 | counter 7 | }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /ng-redux-counter-example/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | var sassLoaders = [ 6 | 'css-loader', 7 | 'autoprefixer-loader?browsers=last 2 version', 8 | 'sass-loader?includePaths[]=' + path.resolve(__dirname, './src'), 9 | ]; 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | entry: [ 14 | 'webpack-dev-server/client?http://localhost:3000', 15 | 'webpack/hot/dev-server', 16 | './src/index' 17 | ], 18 | output: { 19 | path: path.join(__dirname, 'dist'), 20 | filename: 'bundle.js', 21 | publicPath: '/static/' 22 | }, 23 | plugins: [ 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NoErrorsPlugin() 26 | ], 27 | resolve: { 28 | extensions: ['', '.js'], 29 | alias: { 30 | 'react': path.join(__dirname, '..', '..', 'node_modules', 'react') 31 | } 32 | }, 33 | module: { 34 | loaders: [{ 35 | test: /\.js$/, 36 | loaders: ['babel'], 37 | exclude: /node_modules/ 38 | }, { 39 | // HTML LOADER 40 | // Reference: https://github.com/webpack/raw-loader 41 | // Allow loading html through js 42 | test: /\.html$/, 43 | loader: 'raw' 44 | }] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /ng-redux-http-example/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | max_line_length = 80 11 | indent_brace_style = 1TBS 12 | spaces_around_operators = true 13 | quote_type = auto 14 | -------------------------------------------------------------------------------- /ng-redux-http-example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "globals": { 9 | "angular": true, 10 | "moment": true, 11 | "R": true, 12 | "inject": true, 13 | "Q": true, 14 | "sinon": true, 15 | "getService": true, 16 | "expect": true, 17 | "xit": true, 18 | "dealoc": true, 19 | "require": true 20 | }, 21 | "rules": { 22 | "camelcase": 2, 23 | "curly": 2, 24 | "brace-style": [2, "1tbs"], 25 | "quotes": [2, "single"], 26 | "semi": [2, "always"], 27 | "object-curly-spacing": [2, "never"], 28 | "array-bracket-spacing": [2, "never"], 29 | "computed-property-spacing": [2, "never"], 30 | "space-infix-ops": 2, 31 | "space-after-keywords": [2, "always"], 32 | "dot-notation": 2, 33 | "eqeqeq": [2, "smart"], 34 | "no-use-before-define": 2, 35 | "no-redeclare": 2, 36 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 37 | "no-floating-decimal": 2, 38 | "no-spaced-func": 2, 39 | "no-extra-parens": 1, 40 | "no-underscore-dangle": 0, 41 | "valid-jsdoc": 1, 42 | "space-after-keywords": 2, 43 | "max-len": [2, 120], 44 | "no-use-before-define": [2, "nofunc"], 45 | "no-warning-comments": 0, 46 | "new-cap": 0, 47 | "strict": 0, 48 | "eol-last": 0, 49 | "semi": 2 50 | }, 51 | "ecmaFeatures": { 52 | "modules": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ng-redux-http-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # Runtime data 5 | pids 6 | *.pid 7 | *.seed 8 | 9 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 10 | .grunt 11 | 12 | # Sass cache folder 13 | .sass-cache 14 | 15 | # Users Environment Variables 16 | .lock-wscript 17 | .idea/ 18 | *~ 19 | \#*# 20 | .#* 21 | *.keystore 22 | *.sw* 23 | .DS_Store 24 | ._* 25 | Thumbs.db 26 | .cache 27 | *.sublime-project 28 | *.sublime-workspace 29 | *swp 30 | *swo 31 | *swn 32 | build 33 | dist 34 | temp 35 | .tmp 36 | node_modules 37 | bower_components 38 | jspm_packages 39 | .test 40 | www 41 | lib/ 42 | 43 | keys.md 44 | .env 45 | 46 | # Cordova 47 | platforms/ 48 | plugins/ 49 | 50 | # emacs 51 | .tern-port 52 | # -*- mode: gitignore; -*- 53 | *~ 54 | \#*\# 55 | /.emacs.desktop 56 | /.emacs.desktop.lock 57 | *.elc 58 | auto-save-list 59 | tramp 60 | .\#* 61 | 62 | # Org-mode 63 | .org-id-locations 64 | *_archive 65 | 66 | # flymake-mode 67 | *_flymake.* 68 | 69 | # eshell files 70 | /eshell/history 71 | /eshell/lastdir 72 | 73 | # elpa packages 74 | /elpa/ 75 | 76 | # reftex files 77 | *.rel 78 | 79 | # AUCTeX auto folder 80 | /auto/ 81 | 82 | # cask packages 83 | .cask/ 84 | -------------------------------------------------------------------------------- /ng-redux-http-example/README.md: -------------------------------------------------------------------------------- 1 | # Redux Counter Example 2 | 3 | `npm run start` 4 | -------------------------------------------------------------------------------- /ng-redux-http-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-redux-counter-example", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node server.js", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "eslint ./src" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "angular": "^1.4.5", 13 | "babel-core": "^5.8.24", 14 | "babel-loader": "^5.3.2", 15 | "ng-redux": "^2.0.2", 16 | "redux": "^2.0.0", 17 | "redux-logger": "^1.0.6", 18 | "redux-thunk": "^0.1.0" 19 | }, 20 | "devDependencies": { 21 | "extract-text-webpack-plugin": "^0.8.2", 22 | "webpack": "^1.12.1", 23 | "webpack-dev-server": "^1.10.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ng-redux-http-example/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | stats: { 9 | colors: true 10 | } 11 | }) 12 | .listen(3000, 'localhost', function (err) { 13 | if (err) { 14 | console.log(err); 15 | } 16 | 17 | console.log('http://localhost:3000/src'); 18 | }); 19 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import requestActions from './request'; 4 | 5 | export default angular 6 | .module('app.actions', []) 7 | .factory('requestActions', requestActions) 8 | .name; 9 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/actions/request.js: -------------------------------------------------------------------------------- 1 | import {bindActionCreators} from 'redux'; 2 | 3 | export const REQUEST_POSTS = 'REQUEST_POSTS'; 4 | export const REQUEST_POSTS_SUCCESS = 'REQUEST_POSTS_SUCCESS'; 5 | export const REQUEST_POSTS_ERROR = 'REQUEST_POSTS_ERROR'; 6 | 7 | export function loadPosts(reddit = 'all') { 8 | return { 9 | // Types of actions to emit before and after 10 | types: ['REQUEST_POSTS', 'REQUEST_POSTS_SUCCESS', 'REQUEST_POSTS_ERROR'], 11 | 12 | // Check the cache (optional): 13 | shouldCallAPI: (state) => !state.posts.data, 14 | 15 | // Configure $http 16 | config: { 17 | method: 'get', 18 | url: `https://www.reddit.com/r/${reddit}.json`, 19 | }, 20 | 21 | // Metadata to inject in begin/end actions 22 | meta: { 23 | timestamp: Date.now() 24 | } 25 | }; 26 | } 27 | 28 | export function forceHttpError() { 29 | return { 30 | types: ['REQUEST_POSTS', 'REQUEST_POSTS_SUCCESS', 'REQUEST_POSTS_ERROR'], 31 | config: { 32 | method: 'get', 33 | url: `https://www.reddit.com/r/garbagejunkredditidontexist.json`, 34 | } 35 | }; 36 | } 37 | 38 | export default function requestActions($ngRedux) { 39 | let actionCreator = { 40 | loadPosts, 41 | forceHttpError 42 | }; 43 | 44 | return bindActionCreators(actionCreator, $ngRedux.dispatch); 45 | } 46 | 47 | requestActions.$inject = ['$ngRedux']; 48 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/components/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import request from './request'; 4 | 5 | export default angular 6 | .module('app.components', [ 7 | request 8 | ]) 9 | .name; 10 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/components/request.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | class RequestController { 4 | constructor($scope, $ngRedux, requestActions) { 5 | 6 | let unsubscribe = $ngRedux.connect(state => ({ 7 | isFetching: state.posts.isFetching, 8 | error: state.posts.error, 9 | posts: state.posts.data 10 | }))(this); 11 | 12 | $scope.$on('$destroy', unsubscribe); 13 | 14 | this.loadPosts = requestActions.loadPosts; 15 | this.forceHttpError = requestActions.forceHttpError; 16 | } 17 | } 18 | 19 | RequestController.$inject = ['$scope', '$ngRedux', 'requestActions']; 20 | 21 | export default angular 22 | .module('app.request', []) 23 | .directive('httpRequest', () => ({ 24 | restrict: 'E', 25 | template: ` 26 |
27 |

Latest Reddit Posts

28 |
31 | An error occured: {{ request.error | json }} 32 |
33 | 59 |

No posts loaded.

60 |
    61 |
  1. 62 | {{ post.data.title }} 63 |
  2. 64 |
65 |
66 | `, 67 | controller: 'RequestController', 68 | controllerAs: 'request' 69 | })) 70 | .controller('RequestController', RequestController) 71 | .name; 72 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux Example 6 | 7 | 8 |
9 |
10 |
11 |

HTTP Example

12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | // redux 4 | import ngRedux from 'ng-redux'; 5 | import rootReducer from './reducers'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import createLogger from 'redux-logger'; 8 | 9 | const logger = createLogger({ 10 | collapsed: true, 11 | level: 'info' 12 | }); 13 | 14 | // angular 15 | import components from './components'; 16 | import actions from './actions'; 17 | import middleware from './middleware'; 18 | 19 | export default angular 20 | .module('app', [ 21 | ngRedux, 22 | components, 23 | actions, 24 | middleware 25 | ]) 26 | .config(($ngReduxProvider) => { 27 | $ngReduxProvider.createStoreWith(rootReducer, [ 28 | logger, 29 | thunkMiddleware, 30 | 'httpMiddleware' 31 | ]); 32 | }) 33 | .name; 34 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/middleware/http.js: -------------------------------------------------------------------------------- 1 | export default function httpMiddleware($http, $timeout) { 2 | function callAPI(config) { 3 | config = angular.extend(config, { 4 | headers: { 5 | 'Content-Type': 'application/json' 6 | } 7 | }); 8 | 9 | return $http(config) 10 | .then(res => res.data); 11 | } 12 | 13 | return ({dispatch, getState}) => next => action => { 14 | const { 15 | types, 16 | config, 17 | shouldCallAPI = () => true, 18 | meta = {} 19 | } = action; 20 | 21 | if (!types) { 22 | // Normal action: pass it on 23 | return next(action); 24 | } 25 | 26 | if ( 27 | !Array.isArray(types) || 28 | types.length !== 3 || 29 | !types.every(type => typeof type === 'string') 30 | ) { 31 | throw new Error('Expected an array of three string types.'); 32 | } 33 | 34 | if (typeof config !== 'object') { 35 | throw new Error('Expected config to be an object. See $http config.'); 36 | } 37 | 38 | if (!shouldCallAPI(getState())) { 39 | return; 40 | } 41 | 42 | const [requestType, successType, failureType] = types; 43 | 44 | dispatch(Object.assign({}, { 45 | type: requestType, 46 | payload: { 47 | meta 48 | } 49 | })); 50 | 51 | return callAPI(config).then( 52 | response => dispatch(Object.assign({}, { 53 | type: successType, 54 | payload: { 55 | response, 56 | meta 57 | } 58 | })), 59 | error => dispatch(Object.assign({}, { 60 | type: failureType, 61 | error: true, 62 | payload: { 63 | error: error.data, 64 | meta 65 | } 66 | })) 67 | ); 68 | } 69 | } 70 | 71 | httpMiddleware.$inject = ['$http', '$timeout']; 72 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import http from './http'; 4 | 5 | export default angular 6 | .module('app.middleware', []) 7 | .factory('httpMiddleware', http) 8 | .name; 9 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import posts from './posts'; 4 | 5 | const rootReducer = combineReducers({ 6 | posts 7 | }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /ng-redux-http-example/src/reducers/posts.js: -------------------------------------------------------------------------------- 1 | import {REQUEST_POSTS, REQUEST_POSTS_SUCCESS, REQUEST_POSTS_ERROR} from '../actions/request'; 2 | 3 | const INITIAL_STATE = { 4 | isFetching: false, 5 | error: false, 6 | data: null 7 | }; 8 | 9 | export default function posts(state = INITIAL_STATE, action) { 10 | const {payload} = action; 11 | 12 | var actions = { 13 | [REQUEST_POSTS]: (state) => { 14 | return Object.assign({}, state, { 15 | isFetching: true, 16 | error: false 17 | }); 18 | }, 19 | [REQUEST_POSTS_SUCCESS]: (state) => { 20 | return Object.assign({}, state, { 21 | isFetching: false, 22 | data: payload.response.data.children 23 | }); 24 | }, 25 | [REQUEST_POSTS_ERROR]: (state) => { 26 | return Object.assign({}, state, { 27 | isFetching: false, 28 | error: payload.error 29 | }); 30 | }, 31 | }; 32 | 33 | return actions[action.type] ? 34 | actions[action.type](state) : 35 | state; 36 | } 37 | -------------------------------------------------------------------------------- /ng-redux-http-example/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | var sassLoaders = [ 6 | 'css-loader', 7 | 'autoprefixer-loader?browsers=last 2 version', 8 | 'sass-loader?includePaths[]=' + path.resolve(__dirname, './src'), 9 | ]; 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | entry: [ 14 | 'webpack-dev-server/client?http://localhost:3000', 15 | 'webpack/hot/dev-server', 16 | './src/index' 17 | ], 18 | output: { 19 | path: path.join(__dirname, 'dist'), 20 | filename: 'bundle.js', 21 | publicPath: '/static/' 22 | }, 23 | plugins: [ 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NoErrorsPlugin() 26 | ], 27 | resolve: { 28 | extensions: ['', '.js'], 29 | alias: { 30 | 'react': path.join(__dirname, '..', '..', 'node_modules', 'react') 31 | } 32 | }, 33 | module: { 34 | loaders: [{ 35 | test: /\.js$/, 36 | loaders: ['babel'], 37 | exclude: /node_modules/ 38 | }, { 39 | // HTML LOADER 40 | // Reference: https://github.com/webpack/raw-loader 41 | // Allow loading html through js 42 | test: /\.html$/, 43 | loader: 'raw' 44 | }] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "loose": ["es6.modules", "es6.classes"], 4 | "ignore": [ 5 | "foo.js", 6 | "bar/**/*.js" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | max_line_length = 80 11 | indent_brace_style = 1TBS 12 | spaces_around_operators = true 13 | quote_type = auto 14 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "globals": { 9 | "angular": true, 10 | "moment": true, 11 | "R": true, 12 | "inject": true, 13 | "Q": true, 14 | "sinon": true, 15 | "getService": true, 16 | "expect": true, 17 | "xit": true, 18 | "dealoc": true, 19 | "require": true 20 | }, 21 | "rules": { 22 | "camelcase": 2, 23 | "curly": 2, 24 | "brace-style": [2, "1tbs"], 25 | "quotes": [2, "single"], 26 | "semi": [2, "always"], 27 | "object-curly-spacing": [2, "never"], 28 | "array-bracket-spacing": [2, "never"], 29 | "computed-property-spacing": [2, "never"], 30 | "space-infix-ops": 2, 31 | "space-after-keywords": [2, "always"], 32 | "dot-notation": 2, 33 | "eqeqeq": [2, "smart"], 34 | "no-use-before-define": 2, 35 | "no-redeclare": 2, 36 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 37 | "no-floating-decimal": 2, 38 | "no-spaced-func": 2, 39 | "no-extra-parens": 1, 40 | "no-underscore-dangle": 0, 41 | "valid-jsdoc": 1, 42 | "space-after-keywords": 2, 43 | "max-len": [2, 120], 44 | "no-use-before-define": [2, "nofunc"], 45 | "no-warning-comments": 0, 46 | "new-cap": 0, 47 | "strict": 0, 48 | "eol-last": 0, 49 | "semi": 2 50 | }, 51 | "ecmaFeatures": { 52 | "modules": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # Runtime data 5 | pids 6 | *.pid 7 | *.seed 8 | 9 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 10 | .grunt 11 | 12 | # Sass cache folder 13 | .sass-cache 14 | 15 | # Users Environment Variables 16 | .lock-wscript 17 | .idea/ 18 | *~ 19 | \#*# 20 | .#* 21 | *.keystore 22 | *.sw* 23 | .DS_Store 24 | ._* 25 | Thumbs.db 26 | .cache 27 | *.sublime-project 28 | *.sublime-workspace 29 | *swp 30 | *swo 31 | *swn 32 | build 33 | dist 34 | temp 35 | .tmp 36 | node_modules 37 | bower_components 38 | jspm_packages 39 | .test 40 | www 41 | lib/ 42 | 43 | keys.md 44 | .env 45 | 46 | # Cordova 47 | platforms/ 48 | plugins/ 49 | 50 | # emacs 51 | .tern-port 52 | # -*- mode: gitignore; -*- 53 | *~ 54 | \#*\# 55 | /.emacs.desktop 56 | /.emacs.desktop.lock 57 | *.elc 58 | auto-save-list 59 | tramp 60 | .\#* 61 | 62 | # Org-mode 63 | .org-id-locations 64 | *_archive 65 | 66 | # flymake-mode 67 | *_flymake.* 68 | 69 | # eshell files 70 | /eshell/history 71 | /eshell/lastdir 72 | 73 | # elpa packages 74 | /elpa/ 75 | 76 | # reftex files 77 | *.rel 78 | 79 | # AUCTeX auto folder 80 | /auto/ 81 | 82 | # cask packages 83 | .cask/ 84 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/README.md: -------------------------------------------------------------------------------- 1 | # Redux Counter Example 2 | 3 | `npm run start` 4 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-redux-counter-example", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node server.js", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "eslint ./src" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "angular": "^1.4.5", 13 | "axios": "^0.6.0", 14 | "babel-core": "^5.8.24", 15 | "babel-loader": "^5.3.2", 16 | "cors": "^2.7.1", 17 | "express": "^4.13.3", 18 | "invariant": "^2.1.1", 19 | "ng-redux": "^3.0.1", 20 | "proxy-middleware": "^0.14.0", 21 | "q": "^1.4.1", 22 | "redux": "^3.0.0", 23 | "redux-actions": "^0.8.0", 24 | "redux-logger": "^1.0.6", 25 | "redux-thunk": "^0.1.0", 26 | "url": "^0.11.0" 27 | }, 28 | "devDependencies": { 29 | "extract-text-webpack-plugin": "^0.8.2", 30 | "webpack": "^1.12.1", 31 | "webpack-dev-server": "^1.10.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | var express = require('express'); 6 | var proxy = require('proxy-middleware'); 7 | var url = require('url'); 8 | var cors = require('cors'); 9 | 10 | var app = express(); 11 | 12 | app.use(cors()); 13 | 14 | app.use('/api', proxy(url.parse('http://www.nhl.com/stats/rest/grouped/skaters/season/skatersummary?cayenneExp=seasonId=20142015%20and%20gameTypeId=2'))); 15 | app.use('/api2', proxy(url.parse('http://www.nhl.com/stats/rest/grouped/teams/season/teamsummary?cayenneExp=seasonId=20142015%20and%20gameTypeId=2'))); 16 | 17 | app.get('/*', function(req, res) { 18 | res.sendFile(__dirname + '/index.html'); 19 | }); 20 | 21 | var server = new WebpackDevServer(webpack(config), { 22 | publicPath: config.output.publicPath, 23 | hot: true, 24 | quiet: false, 25 | noInfo: false, 26 | stats: { 27 | colors: true 28 | } 29 | }) 30 | 31 | server.listen(8081, 'localhost', function() { 32 | console.log('http://localhost:8081/src'); 33 | }); 34 | app.listen(8080); 35 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/actions/promise.js: -------------------------------------------------------------------------------- 1 | import {bindActionCreators} from 'redux'; 2 | import {createAction} from 'redux-actions'; 3 | import { 4 | REQUEST_PLAYER_INFO, 5 | REQUEST_PLAYER_INFO_SUCCESS, 6 | REQUEST_PLAYER_INFO_ERROR, 7 | REQUEST_PLAYER_TEAM_DATA, 8 | REQUEST_PLAYER_TEAM_DATA_SUCCESS, 9 | REQUEST_PLAYER_TEAM_DATA_ERROR 10 | } from '../constants'; 11 | import webAPI from '../api'; 12 | 13 | export function getPlayerInfo(id) { 14 | id = parseInt(id); 15 | 16 | return { 17 | types: [ 18 | REQUEST_PLAYER_INFO, 19 | REQUEST_PLAYER_INFO_SUCCESS, 20 | REQUEST_PLAYER_INFO_ERROR 21 | ], 22 | payload: { 23 | promise: webAPI.getPlayerInfo(id), 24 | data: id 25 | } 26 | }; 27 | } 28 | 29 | export function getPlayerTeamData(id) { 30 | id = parseInt(id); 31 | 32 | return { 33 | types: [ 34 | REQUEST_PLAYER_TEAM_DATA, 35 | REQUEST_PLAYER_TEAM_DATA_SUCCESS, 36 | REQUEST_PLAYER_TEAM_DATA_ERROR 37 | ], 38 | payload: { 39 | promise: webAPI.getPlayerTeamData(id), 40 | data: id 41 | } 42 | }; 43 | } 44 | 45 | export default { 46 | getPlayerInfo, 47 | getPlayerTeamData 48 | }; 49 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import q from 'q'; 3 | 4 | export function getPlayerInfo(playerId) { 5 | return axios.get('http://localhost:8080/api') 6 | .then(res => res.data.data) 7 | .then(data => data.filter(i => i.playerId === playerId)); 8 | } 9 | 10 | export function getAllTeamsInfo() { 11 | return axios.get('http://localhost:8080/api2') 12 | .then(res => res.data.data); 13 | } 14 | 15 | export function getPlayerTeamData(id) { 16 | return q.all([ 17 | getPlayerInfo(id), 18 | getAllTeamsInfo() 19 | ]) 20 | .then(([players, teams]) => { 21 | const player = players[0]; 22 | 23 | return teams 24 | .filter(i => i.teamAbbrev === player.playerTeamsPlayedFor) 25 | .sort((a, b) => b.seasonId - a.seasonId); 26 | }); 27 | } 28 | 29 | export default { 30 | getPlayerInfo, 31 | getAllTeamsInfo, 32 | getPlayerTeamData 33 | }; 34 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/components/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import request from './request'; 4 | 5 | export default angular 6 | .module('app.components', [ 7 | request 8 | ]) 9 | .name; 10 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/components/request.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import promiseActions from '../actions/promise'; 3 | 4 | class RequestController { 5 | constructor($scope, $ngRedux) { 6 | 7 | let unsubscribe = $ngRedux.connect( 8 | state => ({ 9 | player: state.player, 10 | team: state.team 11 | }), 12 | promiseActions 13 | )(this); 14 | 15 | $scope.$on('$destroy', unsubscribe); 16 | } 17 | 18 | playerId = '8470599' 19 | } 20 | 21 | RequestController.$inject = ['$scope', '$ngRedux']; 22 | 23 | export default angular 24 | .module('app.request', []) 25 | .directive('httpRequest', () => ({ 26 | restrict: 'E', 27 | template: ` 28 |
29 |
30 | 31 | 37 |
38 |
{{ request | json }}
39 |
42 | An error occured: {{ request.player.error | json }} 43 |
44 | 70 |
71 |
    72 |
  1. 73 | {{ team.teamFullName }} {{ team.seasonId }} {{ team.wins }} {{ team.losses }} 74 |
  2. 75 |
76 |
77 |
78 | `, 79 | controller: 'RequestController', 80 | controllerAs: 'request' 81 | })) 82 | .controller('RequestController', RequestController) 83 | .name; 84 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const REQUEST_PLAYER_INFO = 'REQUEST_PLAYER_INFO'; 2 | export const REQUEST_PLAYER_INFO_SUCCESS = 'REQUEST_PLAYER_INFO_SUCCESS'; 3 | export const REQUEST_PLAYER_INFO_ERROR = 'REQUEST_PLAYER_INFO_ERROR'; 4 | 5 | export const REQUEST_PLAYER_TEAM_DATA = 'REQUEST_PLAYER_TEAM_DATA'; 6 | export const REQUEST_PLAYER_TEAM_DATA_SUCCESS = 'REQUEST_PLAYER_TEAM_DATA_SUCCESS'; 7 | export const REQUEST_PLAYER_TEAM_DATA_ERROR = 'REQUEST_PLAYER_TEAM_DATA_ERROR'; 8 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redux Example 6 | 7 | 8 |
9 |
10 |
11 |

Complex API

12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | // redux 4 | import ngRedux from 'ng-redux'; 5 | import rootReducer from './reducers'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import createLogger from 'redux-logger'; 8 | 9 | const logger = createLogger({ 10 | collapsed: true, 11 | level: 'info' 12 | }); 13 | 14 | // angular 15 | import components from './components'; 16 | import middleware from './middleware'; 17 | 18 | export default angular 19 | .module('app', [ 20 | ngRedux, 21 | components, 22 | middleware 23 | ]) 24 | .config(($ngReduxProvider) => { 25 | $ngReduxProvider.createStoreWith(rootReducer, [ 26 | logger, 27 | 'promiseMiddleware', 28 | thunkMiddleware, 29 | ]); 30 | }) 31 | .name; 32 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import promiseMiddleware from './promise'; 3 | 4 | export default angular 5 | .module('app.middleware', []) 6 | .factory('promiseMiddleware', promiseMiddleware) 7 | .name; 8 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/middleware/promise.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import invariant from 'invariant'; 3 | 4 | function isPromise(value) { 5 | return angular.isDefined(value) && 6 | angular.isObject(value) && 7 | angular.isFunction(value.then); 8 | } 9 | 10 | export default function promiseMiddleware() { 11 | return ({dispatch, getState}) => next => action => { 12 | const { 13 | types, 14 | config, 15 | meta = {}, 16 | payload 17 | } = action; 18 | 19 | if (!types) { 20 | // Normal action: pass it on 21 | return next(action); 22 | } 23 | 24 | invariant( 25 | isPromise(payload.promise), 26 | 'Expected payload.promise to be a promise.' 27 | ); 28 | 29 | invariant( 30 | angular.isArray(types) && 31 | types.length === 3 && 32 | types.every(i => angular.isString(i)), 33 | 'Expected an array of three string types.' 34 | ); 35 | 36 | const [ 37 | requestType, 38 | successType, 39 | failureType 40 | ] = types; 41 | 42 | dispatch({ 43 | type: requestType, 44 | payload 45 | }); 46 | 47 | return payload.promise.then( 48 | response => dispatch({ 49 | type: successType, 50 | payload: response 51 | }), 52 | error => dispatch({ 53 | type: failureType, 54 | error: true, 55 | payload: { 56 | error: error.data, 57 | meta 58 | } 59 | }) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import player from './player'; 4 | import team from './team'; 5 | 6 | const rootReducer = combineReducers({ 7 | player, 8 | team 9 | }); 10 | 11 | export default rootReducer; 12 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/reducers/player.js: -------------------------------------------------------------------------------- 1 | import {handleActions} from 'redux-actions'; 2 | import { 3 | REQUEST_PLAYER_INFO, 4 | REQUEST_PLAYER_INFO_SUCCESS, 5 | REQUEST_PLAYER_INFO_ERROR 6 | } from '../constants'; 7 | 8 | const playerReducer = handleActions({ 9 | REQUEST_PLAYER_INFO: (state) => { 10 | return Object.assign({}, state, { 11 | playerInfo: null, 12 | isFetching: true, 13 | hasError: false 14 | }); 15 | }, 16 | REQUEST_PLAYER_INFO_SUCCESS: (state, action) => { 17 | return Object.assign({}, state, { 18 | playerInfo: action.payload, 19 | isFetching: false, 20 | hasError: false 21 | }); 22 | }, 23 | REQUEST_PLAYER_INFO_ERROR: (state) => { 24 | return Object.assign({}, state, { 25 | playerInfo: null, 26 | isFetching: false, 27 | hasError: true 28 | }); 29 | } 30 | }, { 31 | isFetching: false, 32 | playerInfo: null, 33 | hasError: false 34 | }); 35 | 36 | export default playerReducer; 37 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/src/reducers/team.js: -------------------------------------------------------------------------------- 1 | import {handleActions} from 'redux-actions'; 2 | import { 3 | REQUEST_PLAYER_TEAM_DATA, 4 | REQUEST_PLAYER_TEAM_DATA_SUCCESS, 5 | REQUEST_PLAYER_TEAM_DATA_ERROR 6 | } from '../constants'; 7 | 8 | const teamReducer = handleActions({ 9 | REQUEST_PLAYER_TEAM_DATA: (state) => { 10 | return Object.assign({}, state, { 11 | teamInfo: null, 12 | isFetching: true, 13 | hasError: false 14 | }); 15 | }, 16 | REQUEST_PLAYER_TEAM_DATA_SUCCESS: (state, action) => { 17 | return Object.assign({}, state, { 18 | teamInfo: action.payload, 19 | isFetching: false, 20 | hasError: false 21 | }); 22 | }, 23 | REQUEST_PLAYER_TEAM_DATA_ERROR: (state) => { 24 | return Object.assign({}, state, { 25 | teamInfo: null, 26 | isFetching: false, 27 | hasError: true 28 | }); 29 | } 30 | }, { 31 | isFetching: false, 32 | teamInfo: null, 33 | hasError: false 34 | }); 35 | 36 | export default teamReducer; 37 | -------------------------------------------------------------------------------- /ng-redux-multiple-requests/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | var sassLoaders = [ 6 | 'css-loader', 7 | 'autoprefixer-loader?browsers=last 2 version', 8 | 'sass-loader?includePaths[]=' + path.resolve(__dirname, './src'), 9 | ]; 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | entry: [ 14 | 'webpack-dev-server/client?http://localhost:8081', 15 | 'webpack/hot/dev-server', 16 | './src/index' 17 | ], 18 | output: { 19 | path: path.join(__dirname, 'dist'), 20 | filename: 'bundle.js', 21 | publicPath: '/static/' 22 | }, 23 | plugins: [ 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NoErrorsPlugin() 26 | ], 27 | resolve: { 28 | extensions: ['', '.js'], 29 | alias: { 30 | 'react': path.join(__dirname, '..', '..', 'node_modules', 'react') 31 | } 32 | }, 33 | module: { 34 | loaders: [{ 35 | test: /\.js$/, 36 | loaders: ['babel'], 37 | exclude: /node_modules/ 38 | }, { 39 | // HTML LOADER 40 | // Reference: https://github.com/webpack/raw-loader 41 | // Allow loading html through js 42 | test: /\.html$/, 43 | loader: 'raw' 44 | }] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /redux-react-boilerplate/.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 | }, 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "blockBindings": true, 13 | "classes": true, 14 | "defaultParams": true, 15 | "destructuring": true, 16 | "forOf": true, 17 | "generators": false, 18 | "modules": true, 19 | "objectLiteralComputedProperties": true, 20 | "objectLiteralDuplicateProperties": false, 21 | "objectLiteralShorthandMethods": true, 22 | "objectLiteralShorthandProperties": true, 23 | "spread": true, 24 | "superInFunctions": true, 25 | "templateStrings": true, 26 | "jsx": true 27 | }, 28 | "rules": { 29 | /** 30 | * Strict mode 31 | */ 32 | "strict": [2, "never"], // http://eslint.org/docs/rules/strict 33 | 34 | /** 35 | * ES6 36 | */ 37 | "no-var": 2, // http://eslint.org/docs/rules/no-var 38 | "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const 39 | 40 | /** 41 | * Variables 42 | */ 43 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 44 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 45 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 46 | "vars": "local", 47 | "args": "after-used" 48 | }], 49 | "no-use-before-define": 0, // http://eslint.org/docs/rules/no-use-before-define 50 | 51 | /** 52 | * Possible errors 53 | */ 54 | "comma-dangle": [2, "always-multiline"], // http://eslint.org/docs/rules/comma-dangle 55 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 56 | "no-console": 1, // http://eslint.org/docs/rules/no-console 57 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 58 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 59 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 60 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 61 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 62 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 63 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 64 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 65 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 66 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 67 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 68 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 69 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 70 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 71 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 72 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 73 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 74 | "block-scoped-var": 0, // http://eslint.org/docs/rules/block-scoped-var 75 | 76 | /** 77 | * Best practices 78 | */ 79 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 80 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 81 | "default-case": 2, // http://eslint.org/docs/rules/default-case 82 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 83 | "allowKeywords": true 84 | }], 85 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 86 | "guard-for-in": 0, // http://eslint.org/docs/rules/guard-for-in 87 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 88 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 89 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 90 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 91 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 92 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 93 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 94 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 95 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 96 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 97 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 98 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 99 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 100 | "no-new": 2, // http://eslint.org/docs/rules/no-new 101 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 102 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 103 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 104 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 105 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign 106 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 107 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 108 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 109 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 110 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 111 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 112 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 113 | "no-with": 2, // http://eslint.org/docs/rules/no-with 114 | "radix": 2, // http://eslint.org/docs/rules/radix 115 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top 116 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 117 | "yoda": 2, // http://eslint.org/docs/rules/yoda 118 | 119 | /** 120 | * Style 121 | */ 122 | "indent": [2, 2], // http://eslint.org/docs/rules/indent 123 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 124 | "1tbs", { 125 | "allowSingleLine": true 126 | }], 127 | "quotes": [ 128 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 129 | ], 130 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 131 | "properties": "never" 132 | }], 133 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 134 | "before": false, 135 | "after": true 136 | }], 137 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 138 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 139 | "func-names": 1, // http://eslint.org/docs/rules/func-names 140 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 141 | "beforeColon": false, 142 | "afterColon": true 143 | }], 144 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap 145 | "newIsCap": true, 146 | "capIsNew": false 147 | }], 148 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 149 | "max": 2 150 | }], 151 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 152 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 153 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 154 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 155 | "no-extra-parens": [2, "functions"], // http://eslint.org/docs/rules/no-extra-parens 156 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 157 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 158 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 159 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 160 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 161 | "before": false, 162 | "after": true 163 | }], 164 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords 165 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 166 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 167 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 168 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 169 | "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-comment 170 | 171 | /** 172 | * JSX style 173 | */ 174 | "react/display-name": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md 175 | "react/jsx-boolean-value": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 176 | "react/jsx-quotes": 0, // deprecated 177 | "jsx-quotes": [2, "prefer-double"], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-quotes.md 178 | "react/jsx-no-undef": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md 179 | "react/jsx-sort-props": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 180 | "react/jsx-sort-prop-types": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-prop-types.md 181 | "react/jsx-uses-react": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md 182 | "react/jsx-uses-vars": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md 183 | "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 184 | "react/no-did-update-set-state": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md 185 | "react/no-multi-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md 186 | "react/no-unknown-property": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md 187 | "react/prop-types": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md 188 | "react/react-in-jsx-scope": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md 189 | "react/self-closing-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md 190 | "react/wrap-multilines": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md 191 | "react/sort-comp": [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md 192 | "order": [ 193 | "displayName", 194 | "propTypes", 195 | "contextTypes", 196 | "childContextTypes", 197 | "mixins", 198 | "statics", 199 | "defaultProps", 200 | "/^_(?!(on|get|render))/", 201 | "constructor", 202 | "getDefaultProps", 203 | "getInitialState", 204 | "state", 205 | "getChildContext", 206 | "componentWillMount", 207 | "componentDidMount", 208 | "componentWillReceiveProps", 209 | "shouldComponentUpdate", 210 | "componentWillUpdate", 211 | "componentDidUpdate", 212 | "componentWillUnmount", 213 | "/^_?on.+$/", 214 | "/^_?get.+$/", 215 | "/^_?render.+$/", 216 | "render" 217 | ] 218 | }] 219 | } 220 | } -------------------------------------------------------------------------------- /redux-react-boilerplate/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | pids 4 | *.pid 5 | *.seed 6 | lib-cov 7 | coverage 8 | .lock-wscript 9 | build/Release 10 | node_modules 11 | dist/ 12 | tmp/ 13 | build/ 14 | npm-debug.log 15 | -------------------------------------------------------------------------------- /redux-react-boilerplate/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # SCSS Linting 4 | gem 'scss_lint', require: true 5 | -------------------------------------------------------------------------------- /redux-react-boilerplate/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | rainbow (2.0.0) 5 | sass (3.4.18) 6 | scss_lint (0.41.0) 7 | rainbow (~> 2.0) 8 | sass (~> 3.4.15) 9 | 10 | PLATFORMS 11 | ruby 12 | 13 | DEPENDENCIES 14 | scss_lint 15 | 16 | BUNDLED WITH 17 | 1.10.5 18 | -------------------------------------------------------------------------------- /redux-react-boilerplate/README.md: -------------------------------------------------------------------------------- 1 | # Redux React Boilerplate 2 | 3 | ## Coding Guidelines 4 | 5 | If you want to contribute, please refer to the [Rangle.io coding guidelines](http://rangle.io/guidelines/). We also follow a process based on [GitHub Flow](https://guides.github.com/introduction/flow/) where you 6 | 7 | 1. Fork the repo 8 | 2. Make a branch for your feature / chore / fix 9 | 3. Push the branch to your fork and make a Pull Request against master 10 | 4. Get a code review from another team member 11 | 5. Verify that all our CI checks have passed (http://circleci.com) 12 | 6. Merge the branch to main master 13 | 14 | ## Getting Started 15 | 16 | #### Dev 17 | ```bash 18 | $ npm start 19 | ``` 20 | Open http://localhost:8080 in your browser. 21 | 22 | #### Tests 23 | ```bash 24 | $ npm test 25 | $ npm test:watch 26 | ``` 27 | 28 | #### Build 29 | ```bash 30 | $ npm run clean 31 | $ npm run build 32 | $ http-server -p 8080 . 33 | 34 | ``` 35 | Open http://localhost:8080 in your browser. 36 | -------------------------------------------------------------------------------- /redux-react-boilerplate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redux React Boilerplate 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /redux-react-boilerplate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-react-boilerplate", 3 | "version": "0.1.0", 4 | "description": "Boilerplate application for Redux using React.", 5 | "scripts": { 6 | "preinstall": "npm install -g http-server", 7 | "start": "webpack-dev-server --hot --progress", 8 | "clean": "rimraf dist", 9 | "build": "NODE_ENV=production webpack -p", 10 | "test": "", 11 | "test:watch": "" 12 | }, 13 | "devDependencies": { 14 | "babel": "^5.5.8", 15 | "babel-core": "^5.6.18", 16 | "babel-eslint": "^4.1.3", 17 | "babel-loader": "^5.1.4", 18 | "css-loader": "^0.19.0", 19 | "eslint": "^1.5.1", 20 | "eslint-loader": "^1.0.0", 21 | "eslint-plugin-react": "^3.4.2", 22 | "immutable": "^3.7.5", 23 | "node-libs-browser": "^0.5.2", 24 | "node-sass": "^3.3.3", 25 | "radium": "^0.14.1", 26 | "react": "^0.13.3", 27 | "react-hot-loader": "^1.3.0", 28 | "react-redux": "^2.0.0", 29 | "redux": "^2.0.0", 30 | "redux-devtools": "^2.1.2", 31 | "redux-router": "^1.0.0-beta3", 32 | "redux-thunk": "^0.1.0", 33 | "rimraf": "^2.3.4", 34 | "sass-loader": "^2.0.1", 35 | "source-map-loader": "^0.1.5", 36 | "style-loader": "^0.12.4", 37 | "webpack": "^1.12.2", 38 | "webpack-dev-server": "^1.11.0" 39 | }, 40 | "dependencies": { 41 | "history": "^1.10.2", 42 | "ramda": "^0.17.1", 43 | "react-router": "^1.0.0-rc1", 44 | "redux-actions": "^0.8.0", 45 | "redux-logger": "^1.0.9", 46 | "redux-router": "^1.0.0-beta3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/actions/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT, DECREMENT } from '../constants'; 2 | 3 | export function increment() { 4 | return { 5 | type: INCREMENT, 6 | }; 7 | } 8 | 9 | export function decrement() { 10 | return { 11 | type: DECREMENT, 12 | }; 13 | } 14 | 15 | export function incrementIfEven() { 16 | return (dispatch, getState) => { 17 | if (getState().counter % 2 === 0) { 18 | return dispatch(increment()); 19 | } 20 | }; 21 | } 22 | 23 | export function incrementIfOdd() { 24 | return (dispatch, getState) => { 25 | if (getState().counter % 1 === 0) { 26 | return dispatch(increment()); 27 | } 28 | }; 29 | } 30 | 31 | export default { 32 | increment, 33 | decrement, 34 | incrementIfEven, 35 | incrementIfOdd, 36 | }; 37 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/actions/todo.js: -------------------------------------------------------------------------------- 1 | import { ADD_TODO, REMOVE_TODO } from '../constants'; 2 | 3 | export function addTodo(text) { 4 | if (!text || text.length <= 0) { 5 | return false; 6 | } 7 | 8 | return { 9 | type: ADD_TODO, 10 | payload: { 11 | text, 12 | }, 13 | }; 14 | } 15 | 16 | export function removeTodo(text) { 17 | return { 18 | type: REMOVE_TODO, 19 | payload: { 20 | text, 21 | }, 22 | }; 23 | } 24 | 25 | export default { 26 | addTodo, 27 | removeTodo, 28 | }; 29 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/app.js: -------------------------------------------------------------------------------- 1 | import 'babel-core/polyfill'; 2 | import React from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { Route, IndexRoute } from 'react-router'; 5 | import { ReduxRouter } from 'redux-router'; 6 | import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react'; 7 | 8 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 9 | import configureStore from './store/configureStore'; 10 | 11 | import App from './containers/App'; 12 | import Todo from './containers/Todo'; 13 | import Counter from './containers/Counter'; 14 | import About from './containers/About'; 15 | 16 | const routes = ( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | 24 | const history = createBrowserHistory(); 25 | const store = configureStore(routes, history, { counter: 99 }); 26 | 27 | React.render( 28 |
29 | 30 | {() => 31 | 32 | } 33 | 34 | 35 | 38 | 39 |
, 40 | document.getElementById('root') 41 | ); 42 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class Counter extends Component { 4 | 5 | static propTypes = { 6 | counter: PropTypes.number, 7 | increment: PropTypes.func.isRequired, 8 | decrement: PropTypes.func.isRequired, 9 | } 10 | 11 | render() { 12 | const { 13 | counter, 14 | } = this.props; 15 | 16 | return ( 17 |
18 |

{ counter }

19 | 21 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | export default Counter; 29 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/components/TodoForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import R from 'ramda'; 3 | 4 | class TodoForm extends Component { 5 | static propTypes = { 6 | addTodo: PropTypes.func.isRequired, 7 | } 8 | 9 | constructor() { 10 | super(); 11 | 12 | this.state = { 13 | value: '', 14 | }; 15 | } 16 | 17 | onChange(e) { 18 | this.setState({value: e.target.value}); 19 | } 20 | 21 | onSubmit(value) { 22 | this.props.addTodo(value); 23 | } 24 | 25 | render() { 26 | const value = this.state.value; 27 | const onSubmit = R.partial(this.onSubmit, value).bind(this); 28 | 29 | return ( 30 |
31 | 32 | 34 |
35 | ); 36 | } 37 | } 38 | 39 | export default TodoForm; 40 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/components/TodoItem.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda'; 2 | import React, { Component, PropTypes } from 'react'; 3 | 4 | class TodoItem extends Component { 5 | 6 | static propTypes = { 7 | text: PropTypes.string.isRequired, 8 | removeTodo: PropTypes.func.isRequired, 9 | } 10 | 11 | onRemove(text) { 12 | this.props.removeTodo(text); 13 | } 14 | 15 | render() { 16 | const { text } = this.props; 17 | const onRemove = R.partial(this.onRemove, text).bind(this); 18 | 19 | return ( 20 |
  • 21 | { text } 22 |
  • 23 | ); 24 | } 25 | } 26 | 27 | export default TodoItem; 28 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import TodoItem from './TodoItem'; 3 | 4 | class TodoList extends Component { 5 | 6 | static propTypes = { 7 | todos: PropTypes.array.isRequired, 8 | removeTodo: PropTypes.func.isRequired, 9 | } 10 | 11 | render() { 12 | const { 13 | todos, 14 | removeTodo, 15 | } = this.props; 16 | 17 | const todoList = todos.map((i, idx) => { 18 | return ( 19 | 23 | ); 24 | }); 25 | 26 | return ( 27 |
    28 |

    Todo List

    29 |
      30 | { todoList } 31 |
    32 |
    33 | ); 34 | } 35 | } 36 | 37 | export default TodoList; 38 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT = '@@ReduxBoilerplate/INCREMENT'; 2 | export const DECREMENT = '@@ReduxBoilerplate/DECREMENT'; 3 | 4 | export const ADD_TODO = '@@ReduxBoilerplate/ADD_TODO'; 5 | export const REMOVE_TODO = '@@ReduxBoilerplate/REMOVE_TODO'; 6 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/containers/About.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class About extends Component { 4 | 5 | render() { 6 | return ( 7 |
    8 |

    About Us

    9 |

    Lorem ipsum dolor...

    10 |
    11 | ); 12 | } 13 | } 14 | 15 | export default About; 16 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | class App extends Component { 5 | 6 | static propTypes = { 7 | children: PropTypes.node, 8 | }; 9 | 10 | static contextTypes = { 11 | history: PropTypes.object.isRequired, 12 | }; 13 | 14 | render() { 15 | const { children } = this.props; 16 | 17 | return ( 18 |
    19 |
    20 |
      21 |
    • Home
    • 22 |
    • Counter
    • 23 |
    • About
    • 24 |
    25 |
    26 |
    27 | { children } 28 |
    29 |
    30 | ); 31 | } 32 | } 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/containers/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { increment, decrement } from '../actions/counter'; 4 | import Counter from '../components/Counter'; 5 | 6 | class CounterApp extends Component { 7 | 8 | static propTypes = { 9 | counter: PropTypes.number, 10 | increment: PropTypes.func.isRequired, 11 | decrement: PropTypes.func.isRequired, 12 | } 13 | 14 | render() { 15 | return ( 16 |
    17 |

    Counter

    18 | 19 |
    20 | ); 21 | } 22 | } 23 | 24 | function mapStateToProps(state) { 25 | return { 26 | counter: state.counter, 27 | }; 28 | } 29 | 30 | export default connect(mapStateToProps, { increment, decrement })(CounterApp); 31 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/containers/Todo.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { addTodo, removeTodo } from '../actions/todo'; 4 | import TodoList from '../components/TodoList'; 5 | import TodoForm from '../components/TodoForm'; 6 | 7 | class TodoApp extends Component { 8 | 9 | static propTypes = { 10 | todos: PropTypes.array, 11 | addTodo: PropTypes.func.isRequired, 12 | removeTodo: PropTypes.func.isRequired, 13 | } 14 | 15 | render() { 16 | const { todos } = this.props; 17 | const removeTodoFn = this.props.removeTodo; 18 | const addTodoFn = this.props.addTodo; 19 | 20 | return ( 21 |
    22 |

    Todos

    23 | 25 | 28 |
    29 | ); 30 | } 31 | } 32 | 33 | function mapStateToProps(state) { 34 | return { 35 | todos: state.todos, 36 | }; 37 | } 38 | 39 | export default connect(mapStateToProps, { addTodo, removeTodo })(TodoApp); 40 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { INCREMENT, DECREMENT } from '../constants'; 3 | 4 | const INITIAL_STATE = 0; 5 | 6 | const counterReducer = handleActions({ 7 | [INCREMENT]: (state) => state + 1, 8 | [DECREMENT]: (state) => state - 1, 9 | }, INITIAL_STATE); 10 | 11 | export default counterReducer; 12 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerStateReducer } from 'redux-router'; 3 | 4 | import counter from './counter'; 5 | import todos from './todos'; 6 | 7 | const rootReducer = combineReducers({ 8 | counter, 9 | todos, 10 | router: routerStateReducer, 11 | }); 12 | 13 | export default rootReducer; 14 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { ADD_TODO, REMOVE_TODO } from '../constants'; 3 | 4 | const INITIAL_STATE = [ 5 | { 6 | text: 'Learn React', 7 | }, 8 | { 9 | text: 'Learn Flux', 10 | }, 11 | { 12 | text: 'Learn Redux', 13 | }, 14 | ]; 15 | 16 | const todosReducer = handleActions({ 17 | [ADD_TODO]: (state, action) => ([...state, action.payload]), 18 | [REMOVE_TODO]: (state, action) => state.filter(i => i.text !== action.payload.text), 19 | }, INITIAL_STATE); 20 | 21 | export default todosReducer; 22 | -------------------------------------------------------------------------------- /redux-react-boilerplate/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import createLogger from 'redux-logger'; 4 | import rootReducer from '../reducers'; 5 | import { devTools, persistState } from 'redux-devtools'; 6 | import { reduxReactRouter } from 'redux-router'; 7 | 8 | const logger = createLogger({ 9 | collapsed: true, 10 | }); 11 | 12 | export default function configureStore(routes, history, initialState = {}) { 13 | const store = compose( 14 | reduxReactRouter({ 15 | routes, 16 | history, 17 | }), 18 | applyMiddleware(thunkMiddleware, logger), 19 | devTools(), 20 | persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)), 21 | )(createStore)(rootReducer, initialState); 22 | 23 | if (module.hot) { 24 | // Enable Webpack hot module replacement for reducers 25 | module.hot.accept('../reducers', () => { 26 | const nextRootReducer = require('../reducers'); 27 | store.replaceReducer(nextRootReducer); 28 | }); 29 | } 30 | 31 | return store; 32 | } 33 | -------------------------------------------------------------------------------- /redux-react-boilerplate/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | function getEntrySources(sources) { 4 | if (process.env.NODE_ENV !== 'production') { 5 | sources.push('webpack-dev-server/client?http://localhost:8080'); 6 | sources.push('webpack/hot/only-dev-server'); 7 | } 8 | 9 | return sources; 10 | } 11 | 12 | module.exports = { 13 | devtool: process.env.NODE_ENV !== 'production' ? 'eval-source-map' : '', 14 | entry: { 15 | bundle: getEntrySources([ 16 | './src/app.js' 17 | ]) 18 | }, 19 | output: { 20 | publicPath: 'http://localhost:8080/', 21 | filename: 'dist/[name].js' 22 | }, 23 | module: { 24 | preLoaders: [{ 25 | test: /\.js$/, 26 | loader: 'source-map-loader' 27 | }], 28 | loaders: [{ 29 | test: /\.js$/, 30 | loaders: ['react-hot', 'babel-loader?stage=0', 'eslint-loader'], 31 | exclude: /node_modules/ 32 | }, { 33 | test: /\.scss$/, 34 | loaders: ['style', 'css', 'sass'] 35 | }] 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /redux-react-routing-auth/.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 | }, 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "blockBindings": true, 13 | "classes": true, 14 | "defaultParams": true, 15 | "destructuring": true, 16 | "forOf": true, 17 | "generators": false, 18 | "modules": true, 19 | "objectLiteralComputedProperties": true, 20 | "objectLiteralDuplicateProperties": false, 21 | "objectLiteralShorthandMethods": true, 22 | "objectLiteralShorthandProperties": true, 23 | "spread": true, 24 | "superInFunctions": true, 25 | "templateStrings": true, 26 | "jsx": true 27 | }, 28 | "rules": { 29 | /** 30 | * Strict mode 31 | */ 32 | "strict": [2, "never"], // http://eslint.org/docs/rules/strict 33 | 34 | /** 35 | * ES6 36 | */ 37 | "no-var": 2, // http://eslint.org/docs/rules/no-var 38 | "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const 39 | 40 | /** 41 | * Variables 42 | */ 43 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 44 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 45 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 46 | "vars": "local", 47 | "args": "after-used" 48 | }], 49 | "no-use-before-define": 0, // http://eslint.org/docs/rules/no-use-before-define 50 | 51 | /** 52 | * Possible errors 53 | */ 54 | "comma-dangle": [2, "always-multiline"], // http://eslint.org/docs/rules/comma-dangle 55 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 56 | "no-console": 1, // http://eslint.org/docs/rules/no-console 57 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 58 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 59 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 60 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 61 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 62 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 63 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 64 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 65 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 66 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 67 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 68 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 69 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 70 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 71 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 72 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 73 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 74 | "block-scoped-var": 0, // http://eslint.org/docs/rules/block-scoped-var 75 | 76 | /** 77 | * Best practices 78 | */ 79 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 80 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 81 | "default-case": 2, // http://eslint.org/docs/rules/default-case 82 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 83 | "allowKeywords": true 84 | }], 85 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 86 | "guard-for-in": 0, // http://eslint.org/docs/rules/guard-for-in 87 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 88 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 89 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 90 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 91 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 92 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 93 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 94 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 95 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 96 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 97 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 98 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 99 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 100 | "no-new": 2, // http://eslint.org/docs/rules/no-new 101 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 102 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 103 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 104 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 105 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign 106 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 107 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 108 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 109 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 110 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 111 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 112 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 113 | "no-with": 2, // http://eslint.org/docs/rules/no-with 114 | "radix": 2, // http://eslint.org/docs/rules/radix 115 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top 116 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 117 | "yoda": 2, // http://eslint.org/docs/rules/yoda 118 | 119 | /** 120 | * Style 121 | */ 122 | "indent": [2, 2], // http://eslint.org/docs/rules/indent 123 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 124 | "1tbs", { 125 | "allowSingleLine": true 126 | }], 127 | "quotes": [ 128 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 129 | ], 130 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 131 | "properties": "never" 132 | }], 133 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 134 | "before": false, 135 | "after": true 136 | }], 137 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 138 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 139 | "func-names": 1, // http://eslint.org/docs/rules/func-names 140 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 141 | "beforeColon": false, 142 | "afterColon": true 143 | }], 144 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap 145 | "newIsCap": true, 146 | "capIsNew": false 147 | }], 148 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 149 | "max": 2 150 | }], 151 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 152 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 153 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 154 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 155 | "no-extra-parens": [2, "functions"], // http://eslint.org/docs/rules/no-extra-parens 156 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 157 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 158 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 159 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 160 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 161 | "before": false, 162 | "after": true 163 | }], 164 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords 165 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 166 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 167 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 168 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 169 | "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-comment 170 | 171 | /** 172 | * JSX style 173 | */ 174 | "react/display-name": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md 175 | "react/jsx-boolean-value": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 176 | "react/jsx-quotes": 0, // deprecated 177 | "jsx-quotes": [2, "prefer-double"], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-quotes.md 178 | "react/jsx-no-undef": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md 179 | "react/jsx-sort-props": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 180 | "react/jsx-sort-prop-types": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-prop-types.md 181 | "react/jsx-uses-react": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md 182 | "react/jsx-uses-vars": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md 183 | "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 184 | "react/no-did-update-set-state": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md 185 | "react/no-multi-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md 186 | "react/no-unknown-property": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md 187 | "react/prop-types": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md 188 | "react/react-in-jsx-scope": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md 189 | "react/self-closing-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md 190 | "react/wrap-multilines": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md 191 | "react/sort-comp": [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md 192 | "order": [ 193 | "displayName", 194 | "propTypes", 195 | "contextTypes", 196 | "childContextTypes", 197 | "mixins", 198 | "statics", 199 | "defaultProps", 200 | "/^_(?!(on|get|render))/", 201 | "constructor", 202 | "getDefaultProps", 203 | "getInitialState", 204 | "state", 205 | "getChildContext", 206 | "componentWillMount", 207 | "componentDidMount", 208 | "componentWillReceiveProps", 209 | "shouldComponentUpdate", 210 | "componentWillUpdate", 211 | "componentDidUpdate", 212 | "componentWillUnmount", 213 | "/^_?on.+$/", 214 | "/^_?get.+$/", 215 | "/^_?render.+$/", 216 | "render" 217 | ] 218 | }] 219 | } 220 | } -------------------------------------------------------------------------------- /redux-react-routing-auth/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | pids 4 | *.pid 5 | *.seed 6 | lib-cov 7 | coverage 8 | .lock-wscript 9 | build/Release 10 | node_modules 11 | dist/ 12 | tmp/ 13 | build/ 14 | npm-debug.log 15 | -------------------------------------------------------------------------------- /redux-react-routing-auth/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # SCSS Linting 4 | gem 'scss_lint', require: true 5 | -------------------------------------------------------------------------------- /redux-react-routing-auth/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | rainbow (2.0.0) 5 | sass (3.4.18) 6 | scss_lint (0.41.0) 7 | rainbow (~> 2.0) 8 | sass (~> 3.4.15) 9 | 10 | PLATFORMS 11 | ruby 12 | 13 | DEPENDENCIES 14 | scss_lint 15 | 16 | BUNDLED WITH 17 | 1.10.5 18 | -------------------------------------------------------------------------------- /redux-react-routing-auth/README.md: -------------------------------------------------------------------------------- 1 | # Redux Routing Auth Example 2 | 3 | ## Description 4 | 5 | 6 | -------------------------------------------------------------------------------- /redux-react-routing-auth/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redux React Boilerplate 4 | 5 | 6 |
    7 |
    8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /redux-react-routing-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-routing-auth", 3 | "version": "0.1.0", 4 | "description": "Boilerplate application for Redux using React.", 5 | "scripts": { 6 | "preinstall": "npm install -g http-server", 7 | "start": "webpack-dev-server --hot --progress", 8 | "clean": "rimraf dist", 9 | "build": "NODE_ENV=production webpack -p", 10 | "test": "", 11 | "test:watch": "" 12 | }, 13 | "devDependencies": { 14 | "babel": "^5.5.8", 15 | "babel-core": "^5.6.18", 16 | "babel-eslint": "^4.1.3", 17 | "babel-loader": "^5.1.4", 18 | "css-loader": "^0.19.0", 19 | "eslint": "^1.5.1", 20 | "eslint-loader": "^1.0.0", 21 | "eslint-plugin-react": "^3.4.2", 22 | "node-libs-browser": "^0.5.2", 23 | "node-sass": "^3.3.3", 24 | "react-hot-loader": "^1.3.0", 25 | "rimraf": "^2.3.4", 26 | "sass-loader": "^2.0.1", 27 | "source-map-loader": "^0.1.5", 28 | "style-loader": "^0.12.4", 29 | "webpack": "^1.12.2", 30 | "webpack-dev-server": "^1.11.0" 31 | }, 32 | "dependencies": { 33 | "basscss": "^7.0.4", 34 | "history": "^1.10.2", 35 | "immutable": "^3.7.5", 36 | "node-uuid": "^1.4.7", 37 | "radium": "^0.14.1", 38 | "react": "^0.14.2", 39 | "react-dom": "^0.14.2", 40 | "react-redux": "^4.0.0", 41 | "react-router": "^1.0.0", 42 | "redux": "^3.0.0", 43 | "redux-actions": "^0.8.0", 44 | "redux-form": "^3.0.7", 45 | "redux-logger": "^1.0.9", 46 | "redux-router": "^1.0.0-beta3", 47 | "redux-thunk": "^0.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/actions/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT, DECREMENT } from '../constants'; 2 | 3 | export function increment() { 4 | return { 5 | type: INCREMENT, 6 | }; 7 | } 8 | 9 | export function decrement() { 10 | return { 11 | type: DECREMENT, 12 | }; 13 | } 14 | 15 | export function incrementIfEven() { 16 | return (dispatch, getState) => { 17 | if (getState().counter % 2 === 0) { 18 | return dispatch(increment()); 19 | } 20 | }; 21 | } 22 | 23 | export function incrementIfOdd() { 24 | return (dispatch, getState) => { 25 | if (getState().counter % 1 === 0) { 26 | return dispatch(increment()); 27 | } 28 | }; 29 | } 30 | 31 | export default { 32 | increment, 33 | decrement, 34 | incrementIfEven, 35 | incrementIfOdd, 36 | }; 37 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/actions/login.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOGIN_PENDING, 3 | LOGIN_SUCCESS, 4 | LOGIN_ERROR, 5 | LOGOUT, 6 | } from '../constants'; 7 | 8 | import { loginUser } from '../api'; 9 | 10 | export function login(username, password) { 11 | return { 12 | types: [ 13 | LOGIN_PENDING, 14 | LOGIN_SUCCESS, 15 | LOGIN_ERROR, 16 | ], 17 | payload: { 18 | promise: loginUser(username, password), 19 | }, 20 | }; 21 | } 22 | 23 | export function logout() { 24 | return { 25 | type: LOGOUT, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/api/index.js: -------------------------------------------------------------------------------- 1 | import uuid from 'node-uuid'; 2 | 3 | const usersMock = [ 4 | { 5 | username: 'user', 6 | password: 'pass', 7 | }, 8 | ]; 9 | 10 | export function loginUser(username, password) { 11 | return new Promise((resolve, reject) => { 12 | const foundUsers = usersMock.filter((user) => user.username === username); 13 | 14 | const isValid = ( 15 | foundUsers.length === 1 && 16 | foundUsers[0].password === password 17 | ); 18 | 19 | if (isValid) { 20 | setTimeout(() => resolve({ 21 | id: uuid.v1(), 22 | username: username, 23 | }), 750); 24 | } else { 25 | setTimeout(() => reject({ 26 | username, 27 | }), 750); 28 | } 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | import { Provider } from 'react-redux'; 5 | import { Route, IndexRoute } from 'react-router'; 6 | import { ReduxRouter } from 'redux-router'; 7 | 8 | import configureStore from './config/store'; 9 | import history from './config/history'; 10 | 11 | import Main from './containers/Main'; 12 | import Home from './containers/Home'; 13 | import Login from './containers/Login'; 14 | 15 | const routes = ( 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | const store = configureStore(routes, history, { counter: 99 }); 23 | 24 | store.subscribe(() => { 25 | const state = store.getState(); 26 | const currentPath = state.router.location.pathname; 27 | const isLoggedIn = state.session.get('loggedIn'); 28 | 29 | if (currentPath === '/' && !isLoggedIn) { 30 | history.pushState(null, '/login'); 31 | } 32 | 33 | if (currentPath === '/login' && isLoggedIn) { 34 | history.pushState(null, '/'); 35 | } 36 | }); 37 | 38 | render( 39 |
    40 | 41 | 42 | 43 |
    , 44 | document.getElementById('root') 45 | ); 46 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/counter/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Button from '../ui/Button'; 4 | import Title from '../ui/Title'; 5 | 6 | const Counter = ({ increase, decrease, count }) => { 7 | return ( 8 |
    9 | { count } 10 | 11 | 12 |
    13 | ); 14 | }; 15 | 16 | export default Counter; 17 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/login/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { reduxForm } from 'redux-form'; 3 | 4 | import Button from '../ui/Button'; 5 | import Label from '../ui/Label'; 6 | import InputText from '../ui/InputText'; 7 | import FormError from '../ui/FormError'; 8 | import FormGroup from '../ui/FormGroup'; 9 | 10 | const formDefinition = { 11 | form: 'loginForm', 12 | fields: [ 13 | 'username', 14 | 'password', 15 | ], 16 | }; 17 | 18 | const LoginForm = (props) => { 19 | const { 20 | fields: { 21 | username, 22 | password, 23 | }, 24 | hasError, 25 | loginAttempts, 26 | handleSubmit, 27 | } = props; 28 | 29 | return ( 30 |
    31 | 32 | Invalid username / password. You've attempted to login { loginAttempts } times. 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 |
    53 | ); 54 | }; 55 | 56 | export default reduxForm( 57 | formDefinition, 58 | )(LoginForm); 59 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/navigation/Navigator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import NavigatorItem from './NavigatorItem'; 4 | 5 | const Navigator = ({ logoutUser, isLoggedIn, username }) => { 6 | const menuItems = isLoggedIn ? 7 | ( 8 | 9 | { 12 | e.preventDefault(); 13 | logoutUser(); 14 | } } 15 | className="white">Logout 16 | 17 | ) : null; 18 | 19 | return ( 20 |
    21 |
    22 | 23 | React Login 24 | 25 | { menuItems } 26 |
    27 |
    28 | 29 | { username } 30 | 31 |
    32 |
    33 | ); 34 | }; 35 | 36 | export default Navigator; 37 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/navigation/NavigatorItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NavigatorItem = ({ children }) => { 4 | return ( 5 |
    6 | { children } 7 |
    8 | ); 9 | }; 10 | 11 | export default NavigatorItem; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Button = ({ onClick, children }) => { 4 | return ( 5 | 14 | ); 15 | }; 16 | 17 | export default Button; 18 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Content = ({ children }) => { 4 | return ( 5 |
    6 |
    7 | { children } 8 |
    9 |
    10 | ); 11 | }; 12 | 13 | export default Content; 14 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/FormError.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FormError = ({ isVisible, children }) => { 4 | const visibleStyle = isVisible ? 5 | styles.base : styles.hidden; 6 | 7 | return ( 8 |
    9 | { children } 10 |
    11 | ); 12 | }; 13 | 14 | const styles = { 15 | base: { 16 | visibility: 'visible', 17 | opacity: 1, 18 | color: 'white', 19 | fontWeight: 'bold', 20 | padding: '1rem', 21 | marginBottom: '1rem', 22 | backgroundColor: 'red', 23 | }, 24 | hidden: { 25 | visibility: 'hidden', 26 | opacity: 0, 27 | }, 28 | }; 29 | 30 | export default FormError; 31 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/FormGroup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FormGroup = ({ children }) => { 4 | return ( 5 |
    6 | { children } 7 |
    8 | ); 9 | }; 10 | 11 | export default FormGroup; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/InputText.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const InputText = ({ type, binding }) => { 4 | return ( 5 | 9 | ); 10 | }; 11 | 12 | export default InputText; 13 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/Label.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Label = ({ children }) => { 4 | return ( 5 | 8 | ); 9 | }; 10 | 11 | export default Label; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Layout = ({ children }) => { 4 | return ( 5 |
    6 | { children } 7 |
    8 | ); 9 | }; 10 | 11 | export default Layout; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/Text.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Text = ({ children }) => { 4 | return ( 5 |

    6 | { children } 7 |

    8 | ); 9 | }; 10 | 11 | export default Text; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/components/ui/Title.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Title = ({ children }) => { 4 | return ( 5 |

    6 | { children } 7 |

    8 | ); 9 | }; 10 | 11 | export default Title; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/config/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/lib/createHashHistory'; 2 | 3 | export default createHistory({ 4 | queryKey: '_history', 5 | }); 6 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/config/logger.js: -------------------------------------------------------------------------------- 1 | import { Iterable } from 'immutable'; 2 | import createLogger from 'redux-logger'; 3 | 4 | const loggerMiddleware = createLogger({ 5 | collapsed: true, 6 | predicate: (getState, { type }) => { 7 | return type !== 'redux-form/CHANGE' && 8 | type !== 'redux-form/BLUR' && 9 | type !== 'redux-form/TOUCH' && 10 | type !== 'redux-form/FOCUS' && 11 | type !== 'redux-form/DESTROY' && 12 | type !== 'redux-form/START_SUBMIT' && 13 | type !== 'redux-form/STOP_SUBMIT'; 14 | }, 15 | transformer: (state) => { 16 | return Object.keys(state).reduce((newState, key) => { 17 | const val = state[key]; 18 | newState[key] = Iterable.isIterable(val) ? val.toJS() : val; 19 | return newState; 20 | }, {}); 21 | }, 22 | }); 23 | 24 | export default loggerMiddleware; 25 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/config/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { reduxReactRouter } from 'redux-router'; 3 | 4 | import loggerMiddleware from './logger'; 5 | import promiseMiddleware from '../middleware/promiseMiddleware'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import rootReducer from '../reducers'; 8 | 9 | export default function configureStore(routes, history, initialState = {}) { 10 | const store = compose( 11 | reduxReactRouter({ 12 | routes, 13 | history, 14 | }), 15 | applyMiddleware( 16 | promiseMiddleware, 17 | thunkMiddleware, 18 | loggerMiddleware, 19 | ), 20 | )(createStore)(rootReducer, initialState); 21 | 22 | if (module.hot) { 23 | // Enable Webpack hot module replacement for reducers 24 | module.hot.accept('../reducers', () => { 25 | const nextRootReducer = require('../reducers'); 26 | store.replaceReducer(nextRootReducer); 27 | }); 28 | } 29 | 30 | return store; 31 | } 32 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT = '@@ReduxBoilerplate/INCREMENT'; 2 | export const DECREMENT = '@@ReduxBoilerplate/DECREMENT'; 3 | 4 | export const ADD_TODO = '@@ReduxBoilerplate/ADD_TODO'; 5 | export const REMOVE_TODO = '@@ReduxBoilerplate/REMOVE_TODO'; 6 | 7 | export const LOGIN_PENDING = '@@ReduxBoilerplate/LOGIN_PENDING'; 8 | export const LOGIN_SUCCESS = '@@ReduxBoilerplate/LOGIN_SUCCESS'; 9 | export const LOGIN_ERROR = '@@ReduxBoilerplate/LOGIN_ERROR'; 10 | export const LOGOUT = '@@ReduxBoilerplate/LOGOUT'; 11 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/containers/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { increment, decrement } from '../actions/counter'; 5 | import Counter from '../components/counter/Counter'; 6 | import Title from '../components/ui/Title'; 7 | 8 | function mapStateToProps(state) { 9 | return { 10 | count: state.counter, 11 | }; 12 | } 13 | 14 | function mapDispatchToProps(dispatch) { 15 | return { 16 | increase: () => dispatch(increment()), 17 | decrease: () => dispatch(decrement()), 18 | }; 19 | } 20 | 21 | const Home = ({ count, increase, decrease }) => { 22 | return ( 23 |
    24 | Home 25 | 26 | 30 |
    31 | ); 32 | }; 33 | 34 | export default connect( 35 | mapStateToProps, 36 | mapDispatchToProps, 37 | )(Home); 38 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/containers/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { login } from '../actions/login'; 5 | 6 | import Title from '../components/ui/Title'; 7 | import LoginForm from '../components/login/LoginForm'; 8 | 9 | function mapStateToProps(state) { 10 | return { 11 | hasError: state.session.get('hasError'), 12 | loginAttempts: state.session.get('loginAttempts'), 13 | }; 14 | } 15 | 16 | function mapDispatchToProps(dispatch) { 17 | return { 18 | attemptLogin: (username, password) => dispatch(login(username, password)), 19 | }; 20 | } 21 | 22 | const Login = ({ hasError, loginAttempts, attemptLogin }) => { 23 | return ( 24 |
    25 | Login 26 | 27 | attemptLogin(username, password ) } /> 31 |
    32 | ); 33 | }; 34 | 35 | export default connect( 36 | mapStateToProps, 37 | mapDispatchToProps, 38 | )(Login); 39 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/containers/Main.js: -------------------------------------------------------------------------------- 1 | require('../styles/main.scss'); 2 | 3 | import React from 'react'; 4 | import { connect } from 'react-redux'; 5 | 6 | import Navigator from '../components/navigation/Navigator'; 7 | import Layout from '../components/ui/Layout'; 8 | import Content from '../components/ui/Content'; 9 | 10 | import { logout } from '../actions/login'; 11 | 12 | function mapStateToProps(state) { 13 | return { 14 | username: state.session.get('username'), 15 | isLoggedIn: state.session.get('loggedIn'), 16 | }; 17 | } 18 | 19 | function mapDispatchToProps(dispatch) { 20 | return { 21 | logoutUser: () => dispatch(logout()), 22 | }; 23 | } 24 | 25 | const Main = ({ username, isLoggedIn, logoutUser, children }) => { 26 | return ( 27 | 28 | 32 | 33 | { children } 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default connect( 40 | mapStateToProps, 41 | mapDispatchToProps, 42 | )(Main); 43 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/middleware/promiseMiddleware.js: -------------------------------------------------------------------------------- 1 | import { isPromise } from '../utils'; 2 | 3 | export default function promiseMiddleware({ dispatch }) { 4 | return next => action => { 5 | if (!isPromise(action.payload)) { 6 | return next(action); 7 | } 8 | 9 | const { types, payload, meta } = action; 10 | const { promise, data } = payload; 11 | const [ PENDING, FULFILLED, REJECTED ] = types; 12 | 13 | /** 14 | * Dispatch the pending action 15 | */ 16 | dispatch({ 17 | type: PENDING, 18 | ...data && { payload: data }, 19 | ...meta && { meta }, 20 | }); 21 | 22 | /** 23 | * If successful, dispatch the fulfilled action, otherwise dispatch 24 | * rejected action. 25 | */ 26 | return promise.then( 27 | result => { 28 | dispatch({ 29 | type: FULFILLED, 30 | payload: result, 31 | meta, 32 | }); 33 | }, 34 | error => { 35 | dispatch({ 36 | type: REJECTED, 37 | payload: error, 38 | meta, 39 | }); 40 | } 41 | ); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { INCREMENT, DECREMENT } from '../constants'; 3 | 4 | const INITIAL_STATE = 0; 5 | 6 | const counterReducer = handleActions({ 7 | [INCREMENT]: (state) => state + 1, 8 | [DECREMENT]: (state) => state - 1, 9 | }, INITIAL_STATE); 10 | 11 | export default counterReducer; 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { routerStateReducer } from 'redux-router'; 4 | import { reducer as formReducer } from 'redux-form'; 5 | 6 | import counter from './counter'; 7 | import session from './session'; 8 | 9 | const rootReducer = combineReducers({ 10 | counter, 11 | form: formReducer, 12 | router: routerStateReducer, 13 | session, 14 | }); 15 | 16 | export default rootReducer; 17 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/reducers/session.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import { fromJS } from 'immutable'; 3 | 4 | import { 5 | LOGIN_PENDING, 6 | LOGIN_SUCCESS, 7 | LOGIN_ERROR, 8 | LOGOUT, 9 | } from '../constants'; 10 | 11 | const INITIAL_STATE = fromJS({ 12 | loggedIn: false, 13 | hasError: false, 14 | loginAttempts: 0, 15 | sessionId: null, 16 | username: null, 17 | }); 18 | 19 | const sessionReducer = handleActions({ 20 | [LOGIN_PENDING]: (state) => state.merge(fromJS({ 21 | hasError: false, 22 | loginAttempts: state.get('loginAttempts') + 1, 23 | })), 24 | [LOGIN_SUCCESS]: (state, { payload }) => state.merge(fromJS({ 25 | loggedIn: true, 26 | loginAttempts: 0, 27 | sessionId: payload.id, 28 | username: payload.username, 29 | })), 30 | [LOGIN_ERROR]: (state) => state.merge(fromJS({ 31 | loggedIn: false, 32 | hasError: true, 33 | sessionId: null, 34 | username: null, 35 | })), 36 | [LOGOUT]: (state) => state.merge(fromJS({ 37 | loggedIn: false, 38 | sessionId: null, 39 | username: null, 40 | })), 41 | }, INITIAL_STATE); 42 | 43 | export default sessionReducer; 44 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import '../../node_modules/basscss/css/basscss.css'; 2 | -------------------------------------------------------------------------------- /redux-react-routing-auth/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns whether the provided value is a promise 3 | * 4 | * @param {object} value Potential promise 5 | * @return {Boolean} 6 | */ 7 | export function isPromise(value) { 8 | if (value !== null && typeof value === 'object') { 9 | return value.promise && typeof value.promise.then === 'function'; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /redux-react-routing-auth/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | function getEntrySources(sources) { 4 | if (process.env.NODE_ENV !== 'production') { 5 | sources.push('webpack-dev-server/client?http://localhost:8080'); 6 | sources.push('webpack/hot/only-dev-server'); 7 | } 8 | 9 | return sources; 10 | } 11 | 12 | module.exports = { 13 | devtool: process.env.NODE_ENV !== 'production' ? 'eval-source-map' : '', 14 | entry: { 15 | bundle: getEntrySources([ 16 | './src/app.js' 17 | ]) 18 | }, 19 | output: { 20 | publicPath: 'http://localhost:8080/', 21 | filename: 'dist/[name].js' 22 | }, 23 | module: { 24 | preLoaders: [{ 25 | test: /\.js$/, 26 | loader: 'source-map-loader' 27 | }], 28 | loaders: [{ 29 | test: /\.js$/, 30 | loaders: ['react-hot', 'babel-loader?stage=0', 'eslint-loader'], 31 | exclude: /node_modules/ 32 | }, { 33 | test: /\.scss$/, 34 | loaders: ['style', 'css', 'sass'] 35 | }] 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /redux-saga-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /redux-saga-example/.eslintignore: -------------------------------------------------------------------------------- 1 | bin/** 2 | dist/** 3 | build/** 4 | tmp/** 5 | coverage/** 6 | -------------------------------------------------------------------------------- /redux-saga-example/.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 | }, 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "blockBindings": true, 13 | "classes": true, 14 | "defaultParams": true, 15 | "destructuring": true, 16 | "forOf": true, 17 | "generators": false, 18 | "modules": true, 19 | "objectLiteralComputedProperties": true, 20 | "objectLiteralDuplicateProperties": false, 21 | "objectLiteralShorthandMethods": true, 22 | "objectLiteralShorthandProperties": true, 23 | "spread": true, 24 | "superInFunctions": true, 25 | "templateStrings": true, 26 | "jsx": true 27 | }, 28 | "rules": { 29 | /** 30 | * Strict mode 31 | */ 32 | "strict": [2, "never"], // http://eslint.org/docs/rules/strict 33 | 34 | /** 35 | * ES6 36 | */ 37 | "no-var": 2, // http://eslint.org/docs/rules/no-var 38 | "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const 39 | 40 | /** 41 | * Variables 42 | */ 43 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 44 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 45 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 46 | "vars": "local", 47 | "args": "after-used" 48 | }], 49 | "no-use-before-define": 0, // http://eslint.org/docs/rules/no-use-before-define 50 | 51 | /** 52 | * Possible errors 53 | */ 54 | "comma-dangle": [2, "always-multiline"], // http://eslint.org/docs/rules/comma-dangle 55 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 56 | "no-console": 1, // http://eslint.org/docs/rules/no-console 57 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 58 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 59 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 60 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 61 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 62 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 63 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 64 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 65 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 66 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 67 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 68 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 69 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 70 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 71 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 72 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 73 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 74 | "block-scoped-var": 0, // http://eslint.org/docs/rules/block-scoped-var 75 | 76 | /** 77 | * Best practices 78 | */ 79 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 80 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 81 | "default-case": 2, // http://eslint.org/docs/rules/default-case 82 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 83 | "allowKeywords": true 84 | }], 85 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 86 | "guard-for-in": 0, // http://eslint.org/docs/rules/guard-for-in 87 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 88 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 89 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 90 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 91 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 92 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 93 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 94 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 95 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 96 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 97 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 98 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 99 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 100 | "no-new": 2, // http://eslint.org/docs/rules/no-new 101 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 102 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 103 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 104 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 105 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign 106 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 107 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 108 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 109 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 110 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 111 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 112 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 113 | "no-with": 2, // http://eslint.org/docs/rules/no-with 114 | "radix": 2, // http://eslint.org/docs/rules/radix 115 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top 116 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 117 | "yoda": 2, // http://eslint.org/docs/rules/yoda 118 | 119 | /** 120 | * Style 121 | */ 122 | "indent": [2, 2], // http://eslint.org/docs/rules/indent 123 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 124 | "1tbs", { 125 | "allowSingleLine": true 126 | }], 127 | "quotes": [ 128 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 129 | ], 130 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 131 | "properties": "never" 132 | }], 133 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 134 | "before": false, 135 | "after": true 136 | }], 137 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 138 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 139 | "func-names": 1, // http://eslint.org/docs/rules/func-names 140 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 141 | "beforeColon": false, 142 | "afterColon": true 143 | }], 144 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap 145 | "newIsCap": true, 146 | "capIsNew": false 147 | }], 148 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 149 | "max": 2 150 | }], 151 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 152 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 153 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 154 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 155 | "no-extra-parens": [2, "functions"], // http://eslint.org/docs/rules/no-extra-parens 156 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 157 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 158 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 159 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 160 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 161 | "before": false, 162 | "after": true 163 | }], 164 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords 165 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 166 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 167 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 168 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 169 | "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-comment 170 | 171 | /** 172 | * JSX style 173 | */ 174 | "react/display-name": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md 175 | "react/jsx-boolean-value": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 176 | "jsx-quotes": [2, "prefer-double"], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-quotes.md 177 | "react/jsx-no-undef": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md 178 | "react/jsx-sort-props": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 179 | "react/jsx-sort-prop-types": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-prop-types.md 180 | "react/jsx-uses-react": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md 181 | "react/jsx-uses-vars": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md 182 | "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 183 | "react/no-did-update-set-state": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md 184 | "react/no-multi-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md 185 | "react/no-unknown-property": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md 186 | "react/prop-types": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md 187 | "react/react-in-jsx-scope": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md 188 | "react/self-closing-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md 189 | "react/wrap-multilines": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md 190 | "react/sort-comp": [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md 191 | "order": [ 192 | "displayName", 193 | "propTypes", 194 | "contextTypes", 195 | "childContextTypes", 196 | "mixins", 197 | "statics", 198 | "defaultProps", 199 | "/^_(?!(on|get|render))/", 200 | "constructor", 201 | "getDefaultProps", 202 | "getInitialState", 203 | "state", 204 | "getChildContext", 205 | "componentWillMount", 206 | "componentDidMount", 207 | "componentWillReceiveProps", 208 | "shouldComponentUpdate", 209 | "componentWillUpdate", 210 | "componentDidUpdate", 211 | "componentWillUnmount", 212 | "/^_?on.+$/", 213 | "/^_?get.+$/", 214 | "/^_?render.+$/", 215 | "render" 216 | ] 217 | }] 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /redux-saga-example/.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 | 29 | # Build directory 30 | dist 31 | 32 | # Editor temporary files 33 | *~ 34 | \#*# 35 | .#* -------------------------------------------------------------------------------- /redux-saga-example/README.md: -------------------------------------------------------------------------------- 1 | # redux-saga-example 2 | 3 | ## Getting Started 4 | 5 | ``` 6 | npm install 7 | npm run dev 8 | ``` 9 | 10 | ## Resources 11 | 12 | - https://medium.com/infinite-red/using-redux-saga-to-simplify-your-growing-react-native-codebase-2b8036f650de#.elksmclrx 13 | - https://github.com/yelouafi/redux-saga 14 | - http://jaysoo.ca/2016/01/03/managing-processes-in-redux-using-sagas/ 15 | -------------------------------------------------------------------------------- /redux-saga-example/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "ignore": [".git/*", "src/*", "node_modules/*", "dist/*", "build/*"] 4 | } 5 | -------------------------------------------------------------------------------- /redux-saga-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-saga-example", 3 | "version": "0.1.0", 4 | "description": "An example demostration of redux-saga", 5 | "engines": { 6 | "node": "4.x" 7 | }, 8 | "scripts": { 9 | "dev": "NODE_ENV=development nodemon server.js" 10 | }, 11 | "keywords": [ 12 | "react", 13 | "redux" 14 | ], 15 | "license": "MIT", 16 | "devDependencies": { 17 | "nodemon": "^1.8.1", 18 | "webpack-dev-middleware": "^1.2.0", 19 | "webpack-hot-middleware": "^2.5.0" 20 | }, 21 | "dependencies": { 22 | "babel": "^6.3.26", 23 | "babel-core": "^6.4.0", 24 | "babel-eslint": "^4.1.3", 25 | "babel-loader": "^6.2.1", 26 | "babel-polyfill": "^6.3.14", 27 | "babel-preset-es2015": "^6.3.13", 28 | "babel-preset-react": "^6.3.13", 29 | "babel-preset-stage-0": "^6.3.13", 30 | "basscss": "^7.0.4", 31 | "chalk": "^1.1.1", 32 | "classnames": "^2.2.3", 33 | "css-loader": "^0.23.0", 34 | "cssnext-loader": "^1.0.1", 35 | "eslint": "^1.8.0", 36 | "eslint-loader": "^1.1.1", 37 | "eslint-plugin-react": "^3.6.3", 38 | "express": "^4.13.3", 39 | "file-loader": "^0.8.4", 40 | "history": "^1.17.0", 41 | "html-webpack-plugin": "^1.7.0", 42 | "immutable": "^3.7.5", 43 | "json-loader": "^0.5.3", 44 | "postcss-loader": "^0.8.0", 45 | "radium": "^0.14.3", 46 | "react": "^0.14.0", 47 | "react-addons-css-transition-group": "^0.14.7", 48 | "react-addons-test-utils": "^0.14.3", 49 | "react-dom": "^0.14.2", 50 | "react-hot-loader": "^1.3.0", 51 | "react-redux": "^4.0.0", 52 | "react-router": "^1.0.0", 53 | "redux": "^3.0.4", 54 | "redux-form": "^3.0.11", 55 | "redux-localstorage": "^0.4.0", 56 | "redux-logger": "^2.0.4", 57 | "redux-saga": "^0.9.1", 58 | "redux-simple-router": "^1.0.2", 59 | "redux-thunk": "^1.0.0", 60 | "require-hacker": "^2.1.2", 61 | "rimraf": "^2.4.3", 62 | "serve-webpack-client": "^0.0.4", 63 | "source-map-loader": "^0.1.5", 64 | "style-loader": "^0.13.0", 65 | "url-loader": "^0.5.6", 66 | "webpack": "^1.12.2", 67 | "winston": "^2.1.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /redux-saga-example/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const webpack = require('webpack'); 4 | const winston = require('winston'); 5 | 6 | const app = express(); 7 | const PORT = process.env.PORT || 3000; 8 | const DOMAIN = '0.0.0.0'; 9 | 10 | const config = require('./webpack.config'); 11 | const compiler = webpack(config); 12 | 13 | if (process.env.NODE_ENV !== 'production') { 14 | winston.info('Bundling webpack... Please wait.'); 15 | 16 | app.use(require('webpack-dev-middleware')(compiler, { 17 | publicPath: config.output.publicPath, 18 | })); 19 | 20 | app.use(require('webpack-hot-middleware')(compiler)); 21 | } 22 | 23 | app.get('/', (req, res) => { 24 | res.sendFile(path.join(__dirname, '/src/index.html')); 25 | }); 26 | 27 | app.listen(PORT, (err) => { 28 | if (err) { 29 | winston.error(err); 30 | return; 31 | } 32 | 33 | winston.info(`Listening at http://${ DOMAIN }:${ PORT }`); 34 | }); 35 | -------------------------------------------------------------------------------- /redux-saga-example/src/components/Toast.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function Toast({ message, type, onClick }) { 5 | const classes = classNames({ 6 | 'bg-green': type === 0, 7 | 'bg-red': type === 1, 8 | 'bg-orange': type === 2, 9 | }); 10 | 11 | return ( 12 |
    15 |
    { message }
    16 |
    17 | 18 |
    19 |
    20 | ); 21 | } 22 | 23 | Toast.propTypes = { 24 | id: PropTypes.number, 25 | message: PropTypes.string, 26 | type: PropTypes.number, 27 | }; 28 | 29 | Toast.defaultProps = { 30 | onClick: () => {}, 31 | }; 32 | 33 | const styles = { 34 | toast: {}, 35 | }; 36 | -------------------------------------------------------------------------------- /redux-saga-example/src/components/Toaster.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | 3 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | export default class Toaster extends Component { 6 | render() { 7 | const { children } = this.props; 8 | 9 | return ( 10 |
    0 ? styles.visible : styles.hidden, 14 | }}> 15 | 19 | { children } 20 | 21 |
    22 | ); 23 | } 24 | } 25 | 26 | Toaster.propTypes = { 27 | children: PropTypes.node, 28 | }; 29 | 30 | const styles = { 31 | toaster: { 32 | position: 'fixed', 33 | bottom: 0, 34 | right: 0, 35 | zIndex: 4, 36 | margin: '4rem', 37 | transition: 'all 300ms', 38 | width: 240, 39 | }, 40 | visible: { 41 | opacity: 1, 42 | visibility: 'visible', 43 | }, 44 | hidden: { 45 | opacity: 0, 46 | visibility: 'hidden', 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /redux-saga-example/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import generateRandomToast from '../utils/generateRandomToast'; 5 | 6 | import * as toastActions from '../reducers/toasts'; 7 | 8 | import Toaster from '../components/Toaster'; 9 | import Toast from '../components/Toast'; 10 | 11 | function mapStateToProps(state) { 12 | return { 13 | toasts: state.toasts.toList(), 14 | }; 15 | } 16 | 17 | function mapDispatchToProps(dispatch) { 18 | return bindActionCreators(toastActions, dispatch); 19 | } 20 | 21 | function App({ toasts, addToast, burnToast }) { 22 | return ( 23 |
    24 |

    Toaster

    25 | 30 | 31 | 32 |
    { JSON.stringify(toasts, null, 2) }
    33 | 34 | 35 | { 36 | toasts.map(i => { 37 | return ( 38 | burnToast(i.get('id')) } 41 | type={ i.get('type') } 42 | message={ i.get('message') } /> 43 | ); 44 | }) 45 | } 46 | 47 |
    48 | ); 49 | } 50 | 51 | export default connect( 52 | mapStateToProps, 53 | mapDispatchToProps, 54 | )(App); 55 | -------------------------------------------------------------------------------- /redux-saga-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Rangle.io - React / Redux Seed 10 | 11 | 12 |
    13 | 14 | 15 | -------------------------------------------------------------------------------- /redux-saga-example/src/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import './styles/styles.css'; 4 | 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import { Provider } from 'react-redux'; 8 | 9 | import App from './containers/App'; 10 | 11 | import configureStore from './store/configureStore'; 12 | 13 | const store = configureStore({}); 14 | 15 | ReactDOM.render( 16 | 17 | 18 | , 19 | document.getElementById('root') 20 | ); 21 | 22 | -------------------------------------------------------------------------------- /redux-saga-example/src/middleware/logger.js: -------------------------------------------------------------------------------- 1 | import immutableToJS from '../utils/immutableToJS'; 2 | import createLogger from 'redux-logger'; 3 | 4 | const logger = createLogger({ 5 | collapsed: true, 6 | stateTransformer: (state) => { 7 | return immutableToJS(state); 8 | }, 9 | predicate: (getState, { type }) => { 10 | return type !== 'redux-form/BLUR' && 11 | type !== 'redux-form/CHANGE' && 12 | type !== 'redux-form/FOCUS' && 13 | type !== 'redux-form/TOUCH'; 14 | }, 15 | }); 16 | 17 | export default logger; 18 | -------------------------------------------------------------------------------- /redux-saga-example/src/middleware/sagas.js: -------------------------------------------------------------------------------- 1 | import sagaMiddleware from 'redux-saga'; 2 | 3 | import toastQueue from '../sagas/toastQueue'; 4 | 5 | const sagas = sagaMiddleware(toastQueue); 6 | 7 | export default sagas; 8 | -------------------------------------------------------------------------------- /redux-saga-example/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import toasts from './toasts'; 2 | 3 | const reducers = { 4 | toasts, 5 | }; 6 | 7 | export default reducers; 8 | -------------------------------------------------------------------------------- /redux-saga-example/src/reducers/toasts.js: -------------------------------------------------------------------------------- 1 | import { fromJS, OrderedMap } from 'immutable'; 2 | 3 | export const ADD_TOAST = 'ADD_TOAST'; 4 | export const BURN_TOAST = 'BURN_TOAST'; 5 | 6 | let toastID = 0; 7 | const initialState = OrderedMap({}); 8 | 9 | export default function toastReducer(state = initialState, action = {}) { 10 | switch (action.type) { 11 | case ADD_TOAST: 12 | return state.set(action.payload.get('id'), action.payload); 13 | 14 | case BURN_TOAST: 15 | return state.delete(action.payload); 16 | 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export function addToast(toast) { 23 | return { 24 | type: ADD_TOAST, 25 | payload: fromJS({ 26 | id: toastID++, 27 | ...toast, 28 | }), 29 | }; 30 | } 31 | 32 | export function burnToast(id) { 33 | return { 34 | type: BURN_TOAST, 35 | payload: id, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /redux-saga-example/src/sagas/toastQueue.js: -------------------------------------------------------------------------------- 1 | import { takeEvery } from 'redux-saga'; 2 | import { call, put } from 'redux-saga/effects'; 3 | 4 | import { ADD_TOAST, BURN_TOAST } from '../reducers/toasts'; 5 | 6 | const TOAST_TIMEOUT = 5000; 7 | 8 | const wait = ms => ( 9 | new Promise(resolve => { 10 | setTimeout(() => resolve(), ms); 11 | }) 12 | ); 13 | 14 | function* toastQueue() { 15 | yield* takeEvery(ADD_TOAST, onToastAdded); 16 | } 17 | 18 | function* onToastAdded({ payload }) { 19 | if (payload.get('time') !== Infinity) { 20 | yield call(wait, payload.get('time', TOAST_TIMEOUT)); 21 | yield put({ type: BURN_TOAST, payload: payload.get('id') }); 22 | } 23 | } 24 | 25 | export default toastQueue; 26 | -------------------------------------------------------------------------------- /redux-saga-example/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | applyMiddleware, 4 | compose, 5 | combineReducers, 6 | } from 'redux'; 7 | 8 | import thunk from 'redux-thunk'; 9 | import sagas from '../middleware/sagas'; 10 | import logger from '../middleware/logger'; 11 | 12 | import reducers from '../reducers'; 13 | 14 | function configureStore(initialState) { 15 | const finalCreateStore = compose( 16 | applyMiddleware(sagas, thunk, logger), 17 | )(createStore); 18 | 19 | const store = finalCreateStore(combineReducers(reducers), initialState); 20 | 21 | if (module.hot) { 22 | module.hot.accept('../reducers', () => { 23 | const nextRootReducer = require('../reducers'); 24 | store.replaceReducer(nextRootReducer); 25 | }); 26 | } 27 | 28 | return store; 29 | } 30 | 31 | export default configureStore; 32 | -------------------------------------------------------------------------------- /redux-saga-example/src/styles/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | .example-enter { 8 | opacity: 0.01; 9 | } 10 | 11 | .example-enter.example-enter-active { 12 | opacity: 1; 13 | transition: opacity 250ms ease-in; 14 | } 15 | 16 | .example-leave { 17 | opacity: 1; 18 | } 19 | 20 | .example-leave.example-leave-active { 21 | opacity: 0.01; 22 | transition: opacity 250ms ease-in; 23 | } 24 | -------------------------------------------------------------------------------- /redux-saga-example/src/styles/styles.css: -------------------------------------------------------------------------------- 1 | @import 'basscss'; 2 | @import 'base'; 3 | -------------------------------------------------------------------------------- /redux-saga-example/src/utils/generateRandomToast.js: -------------------------------------------------------------------------------- 1 | const messages = [ 2 | ['Successfully added item', 0, 1500], 3 | ['Successfully removed item', 0, 3000], 4 | ['Unable to add item', 1, 5000], 5 | ['Unable to remove item', 1, Infinity], 6 | ]; 7 | 8 | export default function generateRandomToast() { 9 | const message = messages[Math.floor(Math.random() * messages.length)]; 10 | 11 | return { 12 | message: message[0], 13 | type: message[1], 14 | time: message[2], 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /redux-saga-example/src/utils/immutableToJS.js: -------------------------------------------------------------------------------- 1 | import { Iterable } from 'immutable'; 2 | 3 | /** 4 | * [immutableToJS 5 | * converts properties of the provided [state] object from immutable 6 | * data structures to regular JavaScript data structures - used with 7 | * redux-logger 8 | * 9 | * @param {object} state [state reference] 10 | * @return {object} [transformed state] 11 | */ 12 | export default function immutableToJS(state) { 13 | return Object.keys(state).reduce((newState, key) => { 14 | const val = state[key]; 15 | newState[key] = Iterable.isIterable(val) ? val.toJS() : val; 16 | return newState; 17 | }, {}); 18 | } 19 | -------------------------------------------------------------------------------- /redux-saga-example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | function getEntrySources(sources) { 6 | if (process.env.NODE_ENV !== 'production') { 7 | sources.push('webpack-hot-middleware/client'); 8 | } 9 | 10 | return sources; 11 | } 12 | 13 | const basePlugins = [ 14 | new webpack.DefinePlugin({ 15 | __DEV__: process.env.NODE_ENV !== 'production', 16 | __PRODUCTION__: process.env.NODE_ENV === 'production', 17 | }), 18 | new HtmlWebpackPlugin({ 19 | template: './src/index.html', 20 | inject: 'body', 21 | }), 22 | ]; 23 | 24 | const devPlugins = [ 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin(), 27 | ]; 28 | 29 | const prodPlugins = [ 30 | new webpack.optimize.OccurenceOrderPlugin(), 31 | new webpack.optimize.UglifyJsPlugin({ 32 | compressor: { 33 | warnings: false, 34 | }, 35 | }), 36 | ]; 37 | 38 | const plugins = basePlugins 39 | .concat(process.env.NODE_ENV === 'production' ? prodPlugins : []) 40 | .concat(process.env.NODE_ENV === 'development' ? devPlugins : []); 41 | 42 | module.exports = { 43 | entry: { 44 | app: getEntrySources(['./src/index.js']), 45 | }, 46 | 47 | output: { 48 | path: path.join(__dirname, 'dist'), 49 | filename: '[name].[hash].js', 50 | publicPath: '/', 51 | sourceMapFilename: '[name].[hash].js.map', 52 | chunkFilename: '[id].chunk.js', 53 | }, 54 | 55 | devtool: 'source-map', 56 | 57 | plugins: plugins, 58 | 59 | module: { 60 | preLoaders: [ 61 | { 62 | test: /\.js$/, 63 | loader: 'source-map-loader', 64 | }, 65 | ], 66 | loaders: [ 67 | { 68 | test: /\.css$/, 69 | loader: 'style-loader!css-loader!postcss-loader!cssnext-loader', 70 | }, 71 | { 72 | test: /\.js$/, 73 | loaders: ['react-hot', 'babel', 'eslint-loader'], 74 | exclude: /node_modules/, 75 | }, 76 | { 77 | test: /\.json$/, 78 | loader: 'json-loader', 79 | }, 80 | { 81 | test: /\.(png|jpg|jpeg|gif|svg)$/, 82 | loader: 'url-loader?prefix=img/&limit=5000', 83 | }, 84 | { 85 | test: /\.(woff|woff2|ttf|eot)$/, 86 | loader: 'url-loader?prefix=font/&limit=5000', 87 | }, 88 | ], 89 | }, 90 | }; 91 | -------------------------------------------------------------------------------- /reselect-example/example.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const state = { 4 | items: [ 5 | { 6 | id: '1234', 7 | value: 10 8 | }, 9 | { 10 | id: '234', 11 | value: 15 12 | }, 13 | { 14 | id: '234', 15 | value: 65 16 | }, 17 | { 18 | id: '234', 19 | value: 35 20 | }, 21 | { 22 | id: '567', 23 | value: 25 24 | } 25 | ], 26 | taxPercent: 15 27 | }; 28 | 29 | const shopItemsSelector = state => state.items; 30 | const taxPercentSelector = state => state.taxPercent; 31 | 32 | const subtotalSelector = createSelector( 33 | [ 34 | shopItemsSelector 35 | ], 36 | items => items.reduce((acc, item) => acc + item.value, 0) 37 | ); 38 | 39 | const taxSelector = createSelector( 40 | [ 41 | subtotalSelector, 42 | taxPercentSelector 43 | ], 44 | (subtotal, taxPercent) => subtotal * (taxPercent / 100) 45 | ); 46 | 47 | export const totalSelector = createSelector( 48 | [ 49 | subtotalSelector, 50 | taxSelector 51 | ], 52 | (subtotal, tax) => ({total: subtotal + tax}) 53 | ); 54 | 55 | console.log('Provided this state :: \n', state); 56 | 57 | console.log('The subtotal is :: ', subtotalSelector(state)); 58 | console.log('The tax is :: ', taxSelector(state)); 59 | console.log('The total is :: ', totalSelector(state)); 60 | -------------------------------------------------------------------------------- /reselect-example/node_modules/reselect/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [1.1.0](https://github.com/faassen/reselect/releases/tag/v1.1.0) - 2015/09/16 7 | 8 | ## New features 9 | 10 | ### `createStructuredSelector` 11 | 12 | `createStructuredSelector` is a convenience function that helps with a common pattern when using Reselect. The selector passed to a connect decorator often just takes other selectors and maps them to keys in an object: 13 | 14 | ```js 15 | const mySelectorA = state => state.a; 16 | const mySelectorB = state => state.b; 17 | 18 | const structuredSelector = createSelector( 19 | mySelectorA, 20 | mySelectorB, 21 | mySelectorC, 22 | (a, b, c) => ({ 23 | a, 24 | b, 25 | c 26 | }) 27 | ); 28 | ``` 29 | 30 | `createStructuredSelector` takes an object whose properties are input-selectors and returns a structured selector. The structured selector returns an object with the same keys as the `inputSelectors` argument, but with the selectors replaced with their values. 31 | 32 | ```js 33 | const mySelectorA = state => state.a; 34 | const mySelectorB = state => state.b; 35 | 36 | const structuredSelector = createStructuredSelector({ 37 | x: mySelectorA, 38 | y: mySelectorB 39 | }); 40 | 41 | const result = structuredSelector({a: 1, b: 2}); // will produce {x: 1, y: 2} 42 | ``` 43 | 44 | ## [1.0.0](https://github.com/faassen/reselect/releases/tag/v1.0.0) - 2015/09/09 45 | 46 | ## Breaking Changes 47 | 48 | If upgrading from 0.0.2, see the release notes for v1.0.0-alpha 49 | 50 | ## [1.0.0-alpha2](https://github.com/faassen/reselect/releases/tag/v1.0.0-alpha2) - 2015/09/01 51 | 52 | ## New features 53 | 54 | src directory included in npm package 55 | js:next field added to package.json 56 | 57 | ## [1.0.0-alpha](https://github.com/faassen/reselect/releases/tag/v1.0.0-alpha) - 2015/09/01 58 | 59 | ## Breaking Changes 60 | 61 | `createSelectorCreator` takes a user specified memoize function instead of a custom `valueEqualsFunc`. 62 | 63 | ### Before 64 | 65 | ```js 66 | import { isEqual } from 'lodash'; 67 | import { createSelectorCreator } from 'reselect'; 68 | 69 | const deepEqualsSelectorCreator = createSelectorCreator(isEqual); 70 | ``` 71 | 72 | ### After 73 | 74 | ```js 75 | import { isEqual } from 'lodash'; 76 | import { createSelectorCreator, defaultMemoize } from 'reselect'; 77 | 78 | const deepEqualsSelectorCreator = createSelectorCreator( 79 | defaultMemoize, 80 | isEqual 81 | ); 82 | ``` 83 | 84 | ## New features 85 | 86 | ### Variadic Dependencies 87 | 88 | Selector creators can receive a variadic number of dependencies as well as an array of dependencies. 89 | 90 | #### Before 91 | 92 | ```js 93 | const selector = createSelector( 94 | [state => state.a, state => state.b], 95 | (a, b) => a * b 96 | ); 97 | ``` 98 | 99 | #### After 100 | 101 | ```js 102 | const selector = createSelector( 103 | state => state.a, 104 | state => state.b, 105 | (a, b) => a * b 106 | ); 107 | ``` 108 | 109 | ### Access `ownProps` in Selector 110 | 111 | Selector dependencies can receive a variadic number of parameters allowing a selector to receive `ownProps` passed from `mapToProps` in `connect`. 112 | 113 | ```js 114 | const selector = createSelector( 115 | (state) => state.a, 116 | (state, props) => state.b * props.c, 117 | (_, props) => props.d, 118 | (a, bc, d) => a + bc + d 119 | ); 120 | ``` 121 | 122 | ### Configurable Memoize Function 123 | 124 | ```js 125 | import { createSelectorCreator } from 'reselect'; 126 | import memoize from 'lodash.memoize'; 127 | 128 | let called = 0; 129 | const customSelectorCreator = createSelectorCreator(memoize, JSON.stringify); 130 | const selector = customSelectorCreator( 131 | state => state.a, 132 | state => state.b, 133 | (a, b) => { 134 | called++; 135 | return a + b; 136 | } 137 | ); 138 | assert.equal(selector({a: 1, b: 2}), 3); 139 | assert.equal(selector({a: 1, b: 2}), 3); 140 | assert.equal(called, 1); 141 | assert.equal(selector({a: 2, b: 3}), 5); 142 | assert.equal(called, 2); 143 | ``` 144 | -------------------------------------------------------------------------------- /reselect-example/node_modules/reselect/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Reselect developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /reselect-example/node_modules/reselect/README.md: -------------------------------------------------------------------------------- 1 | # Reselect 2 | 3 | Simple "selector" library for Redux inspired by getters in [NuclearJS](https://github.com/optimizely/nuclear-js.git), [subscriptions](https://github.com/Day8/re-frame#just-a-read-only-cursor) in [re-frame](https://github.com/Day8/re-frame) and this [proposal](https://github.com/gaearon/redux/pull/169) from [speedskater](https://github.com/speedskater). 4 | 5 | * Selectors can compute derived data, allowing Redux to store the minimal possible state. 6 | * Selectors are efficient. A selector is not recomputed unless one of its arguments change. 7 | * Selectors are composable. They can be used as input to other selectors. 8 | 9 | ```js 10 | import { createSelector } from 'reselect'; 11 | 12 | const shopItemsSelector = state => state.shop.items; 13 | const taxPercentSelector = state => state.shop.taxPercent; 14 | 15 | const subtotalSelector = createSelector( 16 | shopItemsSelector, 17 | items => items.reduce((acc, item) => acc + item.value, 0) 18 | ); 19 | 20 | const taxSelector = createSelector( 21 | subtotalSelector, 22 | taxPercentSelector, 23 | (subtotal, taxPercent) => subtotal * (taxPercent / 100) 24 | ); 25 | 26 | export const totalSelector = createSelector( 27 | subtotalSelector, 28 | taxSelector, 29 | (subtotal, tax) => { return {total: subtotal + tax}} 30 | ); 31 | ``` 32 | 33 | ## Table of Contents 34 | 35 | - [Installation](#installation) 36 | - [Example](#example) 37 | - [Motivation for Memoized Selectors](#motivation-for-memoized-selectors) 38 | - [Creating a Memoized Selector](#creating-a-memoized-selector) 39 | - [Composing Selectors](#composing-selectors) 40 | - [Connecting a Selector to the Redux Store](#connecting-a-selector-to-the-redux-store) 41 | - [Accessing React Props in Selectors](#accessing-react-props-in-selectors) 42 | - [API](#api) 43 | - [`createSelector`](#createselectorinputselectors-resultfn) 44 | - [`defaultMemoize`](#defaultmemoize-equalitycheck--defaultequalitycheck) 45 | - [`createSelectorCreator`](#createselectorcreatormemoizefunc-memoizeoptions) 46 | - [`createStructuredSelector`](#createstructuredselectorinputselectors-selectorcreator--createselector) 47 | - [FAQ](#faq) 48 | - [Why isn't my selector recomputing when the input state changes?](#q-why-isnt-my-selector-recomputing-when-the-input-state-changes) 49 | - [Why is my selector recomputing when the input state stays the same?](#q-why-is-my-selector-recomputing-when-the-input-state-stays-the-same) 50 | - [Can I use Reselect without Redux?](#q-can-i-use-reselect-without-redux) 51 | - [The default memoization function is no good, can I use a different one?](#q-the-default-memoization-function-is-no-good-can-i-use-a-different-one) 52 | - [How do I test a selector?](#q-how-do-i-test-a-selector) 53 | - [How do I create a selector that takes an argument? ](#q-how-do-i-create-a-selector-that-takes-an-argument) 54 | - [How do I use Reselect with Immutable.js?](#q-how-do-i-use-reselect-with-immutablejs) 55 | - [License](#license) 56 | 57 | ## Installation 58 | npm install reselect 59 | 60 | ## Example 61 | 62 | ### Motivation for Memoized Selectors 63 | 64 | > The examples in this section are based on the [Redux Todos List example](http://rackt.github.io/redux/docs/basics/UsageWithReact.html). 65 | 66 | Consider the following code: 67 | 68 | #### `containers/App.js` 69 | 70 | ```js 71 | import React, { Component, PropTypes } from 'react'; 72 | import { connect } from 'react-redux'; 73 | import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from '../actions'; 74 | import AddTodo from '../components/AddTodo'; 75 | import TodoList from '../components/TodoList'; 76 | import Footer from '../components/Footer'; 77 | 78 | class App extends Component { 79 | render() { 80 | // Injected by connect() call: 81 | const { dispatch, visibleTodos, visibilityFilter } = this.props; 82 | return ( 83 |
    84 | 86 | dispatch(addTodo(text)) 87 | } /> 88 | 91 | dispatch(completeTodo(index)) 92 | } /> 93 |
    96 | dispatch(setVisibilityFilter(nextFilter)) 97 | } /> 98 |
    99 | ); 100 | } 101 | } 102 | 103 | App.propTypes = { 104 | visibleTodos: PropTypes.arrayOf(PropTypes.shape({ 105 | text: PropTypes.string.isRequired, 106 | completed: PropTypes.bool.isRequired 107 | })), 108 | visibilityFilter: PropTypes.oneOf([ 109 | 'SHOW_ALL', 110 | 'SHOW_COMPLETED', 111 | 'SHOW_ACTIVE' 112 | ]).isRequired 113 | }; 114 | 115 | function selectTodos(todos, filter) { 116 | switch (filter) { 117 | case VisibilityFilters.SHOW_ALL: 118 | return todos; 119 | case VisibilityFilters.SHOW_COMPLETED: 120 | return todos.filter(todo => todo.completed); 121 | case VisibilityFilters.SHOW_ACTIVE: 122 | return todos.filter(todo => !todo.completed); 123 | } 124 | } 125 | 126 | function select(state) { 127 | return { 128 | visibleTodos: selectTodos(state.todos, state.visibilityFilter), 129 | visibilityFilter: state.visibilityFilter 130 | }; 131 | } 132 | 133 | // Wrap the component to inject dispatch and state into it 134 | export default connect(select)(App); 135 | ``` 136 | 137 | In the above example, `select` calls `selectTodos` to calculate `visibleTodos`. This works great, but there is a drawback: `visibleTodos` is calculated every time the component is updated. If the state tree is large, or the calculation expensive, repeating the calculation on every update may cause performance problems. Reselect can help to avoid these unnecessary recalculations. 138 | 139 | ### Creating a Memoized Selector 140 | 141 | We would like to replace `select` with a memoized selector that recalculates `visibleTodos` when the value of `state.todos` or `state.visibilityFilter` changes, but not when changes occur in other (unrelated) parts of the state tree. 142 | 143 | Reselect provides a function `createSelector` for creating memoized selectors. `createSelector` takes an array of input-selectors and a transform function as its arguments. If the Redux state tree is mutated in a way that causes the value of an input-selector to change, the selector will call its transform function with the values of the input-selectors as arguments and return the result. If the values of the input-selectors are the same as the previous call to the selector, it will return the previously computed value instead of calling the transform function. 144 | 145 | Let's define a memoized selector named `visibleTodosSelector` to replace `select`: 146 | 147 | #### `selectors/TodoSelectors.js` 148 | 149 | ```js 150 | import { createSelector } from 'reselect'; 151 | import { VisibilityFilters } from '../actions'; 152 | 153 | function selectTodos(todos, filter) { 154 | switch (filter) { 155 | case VisibilityFilters.SHOW_ALL: 156 | return todos; 157 | case VisibilityFilters.SHOW_COMPLETED: 158 | return todos.filter(todo => todo.completed); 159 | case VisibilityFilters.SHOW_ACTIVE: 160 | return todos.filter(todo => !todo.completed); 161 | } 162 | } 163 | 164 | /* 165 | * Definition of input-selectors. 166 | * Input-selectors should be used to abstract away the structure 167 | * of the store in cases where no calculations are needed 168 | * and memoization wouldn't provide any benefits. 169 | */ 170 | const visibilityFilterSelector = state => state.visibilityFilter; 171 | const todosSelector = state => state.todos; 172 | 173 | /* 174 | * Definition of combined-selector. 175 | * In visibleTodosSelector, input-selectors are combined to derive new information. 176 | * To prevent expensive recalculation of the input-selectors memoization is applied. 177 | * Hence, these selectors are only recomputed when the value of their input-selectors change. 178 | * If none of the input-selectors return a new value, the previously computed value is returned. 179 | */ 180 | export const visibleTodosSelector = createSelector( 181 | visibilityFilterSelector, 182 | todosSelector, 183 | (visibilityFilter, todos) => { 184 | return { 185 | visibleTodos: selectTodos(todos, visibilityFilter), 186 | visibilityFilter 187 | }; 188 | } 189 | ); 190 | ``` 191 | 192 | In the example above, `visibilityFilterSelector` and `todosSelector` are input-selectors. They are created as ordinary non-memoized selector functions because they do not transform the data they select. `visibleTodosSelector` on the other hand is a memoized selector. It takes `visibilityFilterSelector` and `todosSelector` as input-selectors, and a transform function that calculates the filtered todos list. 193 | 194 | ### Composing Selectors 195 | 196 | A memoized selector can itself be an input-selector to another memoized selector. Here is `visibleTodosSelector` being used as an input-selector to a selector that further filters the todos by keyword: 197 | 198 | ```js 199 | const keywordSelector = state => state.keyword; 200 | 201 | const keywordFilterSelector = createSelector( 202 | [visibleTodosSelector, keywordSelector], 203 | (visibleTodos, keyword) => visibleTodos.filter( 204 | todo => todo.indexOf(keyword) > -1 205 | ) 206 | ); 207 | ``` 208 | 209 | ### Connecting a Selector to the Redux Store 210 | 211 | If you are using React Redux, you connect a memoized selector to the Redux store using `connect`: 212 | 213 | #### `containers/TodoApp.js` 214 | 215 | ```js 216 | import React, { Component, PropTypes } from 'react'; 217 | import { connect } from 'react-redux'; 218 | import { addTodo, completeTodo, setVisibilityFilter } from '../actions'; 219 | import AddTodo from '../components/AddTodo'; 220 | import TodoList from '../components/TodoList'; 221 | import Footer from '../components/Footer'; 222 | 223 | /* 224 | * Import the selector defined in ../selectors/todoSelectors.js. 225 | * This allows you to separate your components from the structure of your stores. 226 | */ 227 | import { visibleTodosSelector } from '../selectors/todoSelectors'; 228 | 229 | class App extends Component { 230 | render() { 231 | // Injected by connect() call: 232 | const { dispatch, visibleTodos, visibilityFilter } = this.props; 233 | return ( 234 |
    235 | 237 | dispatch(addTodo(text)) 238 | } /> 239 | 242 | dispatch(completeTodo(index)) 243 | } /> 244 |
    247 | dispatch(setVisibilityFilter(nextFilter)) 248 | } /> 249 |
    250 | ); 251 | } 252 | } 253 | 254 | App.propTypes = { 255 | visibleTodos: PropTypes.arrayOf(PropTypes.shape({ 256 | text: PropTypes.string.isRequired, 257 | completed: PropTypes.bool.isRequired 258 | })), 259 | visibilityFilter: PropTypes.oneOf([ 260 | 'SHOW_ALL', 261 | 'SHOW_COMPLETED', 262 | 'SHOW_ACTIVE' 263 | ]).isRequired 264 | }; 265 | 266 | /* 267 | * Connect visibleTodosSelector to the App component. 268 | * The keys of the selector result are available on the props object for App. 269 | * In our example there is the 'visibleTodos' key which is bound to this.props.visibleTodos 270 | */ 271 | export default connect(visibleTodosSelector)(App); 272 | ``` 273 | 274 | ### Accessing React Props in Selectors 275 | 276 | So far we have only seen selectors receive the Redux store state as input, but it is also possible for a selector to receive the props of a component wrapped by `connect`. 277 | 278 | Consider the following example: 279 | 280 | #### `index.js` 281 | 282 | ```js 283 | import React from 'react'; 284 | import { createStore } from 'redux'; 285 | import { Provider } from 'react-redux'; 286 | import App from './containers/App'; 287 | import todoApp from './reducers'; 288 | 289 | let store = createStore(todoApp); 290 | 291 | let rootElement = document.getElementById('root'); 292 | React.render( 293 | 294 | {() => } 295 | , 296 | rootElement 297 | ); 298 | ``` 299 | 300 | We have introduced a prop named `maxTodos` to the `App` component. We would like to access `maxTodos` in `visibleTodosSelector` so we can make sure that we do not return more Todos than it specifies. To achieve this we can make the following changes to `selectors/todoSelectors.js`: 301 | 302 | #### `selectors/todoSelectors.js` 303 | 304 | ```js 305 | import { createSelector } from 'reselect'; 306 | import { VisibilityFilters } from './actions'; 307 | 308 | function selectTodos(todos, filter) { 309 | switch (filter) { 310 | case VisibilityFilters.SHOW_ALL: 311 | return todos; 312 | case VisibilityFilters.SHOW_COMPLETED: 313 | return todos.filter(todo => todo.completed); 314 | case VisibilityFilters.SHOW_ACTIVE: 315 | return todos.filter(todo => !todo.completed); 316 | } 317 | } 318 | 319 | const visibilityFilterSelector = state => state.visibilityFilter; 320 | const todosSelector = state => state.todos; 321 | const maxTodosSelector = (state, props) => props.maxTodos; 322 | 323 | export const visibleTodosSelector = createSelector( 324 | visibilityFilterSelector, 325 | todosSelector, 326 | maxTodosSelector, 327 | (visibilityFilter, todos, maxTodos) => { 328 | const visibleTodos = selectTodos(todos, visibilityFilter).slice(0, maxTodos); 329 | return { 330 | visibleTodos, 331 | visibilityFilter 332 | }; 333 | } 334 | ); 335 | ``` 336 | 337 | When a selector is connected to a component with `connect`, the component props are passed as the second argument to the selector. In `visibleTodosSelector` we have added a new input-selector named `maxTodosSelector`, which returns the `maxTodos` property from its props argument. 338 | 339 | ## API 340 | 341 | ### createSelector(...inputSelectors, resultFunc) 342 | ### createSelector([inputSelectors], resultFunc) 343 | 344 | Takes one or more selectors whose values are computed and passed as arguments to `resultFn`. 345 | 346 | `createSelector` determines if the value returned by an input-selector has changed between calls using reference equality (`===`). Inputs to selectors created with `createSelector` should be immutable. 347 | 348 | Selectors created with `createSelector` have a cache size of 1. This means they always recalculate when the value of an input-selector changes, as a selector only stores the preceding value of each input-selector. 349 | 350 | ```js 351 | const mySelector = createSelector( 352 | state => state.values.value1, 353 | state => state.values.value2, 354 | (value1, value2) => value1 + value2 355 | ); 356 | 357 | // You can also pass an array of selectors 358 | const totalSelector = createSelector( 359 | [ 360 | state => state.values.value1, 361 | state => state.values.value2 362 | ], 363 | (value1, value2) => value1 + value2 364 | ); 365 | ``` 366 | 367 | It can be useful to access the props of a component from within a selector. When a selector is connected to a component with `connect`, the component props are passed as the second argument to the selector: 368 | 369 | ```js 370 | const abSelector = (state, props) => state.a * props.b; 371 | 372 | // props only (ignoring state argument) 373 | const cSelector = (_, props) => props.c; 374 | 375 | // state only (props argument omitted as not required) 376 | const dSelector = state => state.d; 377 | 378 | const totalSelector = createSelector( 379 | abSelector, 380 | cSelector, 381 | dSelector, 382 | (ab, c, d) => ({ 383 | total: ab + c + d 384 | }) 385 | ); 386 | 387 | ``` 388 | 389 | ### defaultMemoize(func, equalityCheck = defaultEqualityCheck) 390 | 391 | `defaultMemoize` memoizes the function passed in the func parameter. It is the memoize function used by `createSelector`. 392 | 393 | `defaultMemoize` has a cache size of 1. This means it always recalculates when the value of an argument changes. 394 | 395 | `defaultMemoize` determines if an argument has changed by calling the `equalityCheck` function. As `defaultMemoize` is designed to be used wiht immutable data, the default `equalityCheck` function checks for changes using reference equality: 396 | 397 | ```js 398 | function defaultEqualityCheck(currentVal, previousVal) { 399 | return currentVal === previousVal; 400 | } 401 | ``` 402 | 403 | `defaultMemoize` can be used with `createSelectorCreator` to [customize the `equalityCheck` function](#customize-equalitycheck-for-defaultmemoize). 404 | 405 | ### createSelectorCreator(memoize, ...memoizeOptions) 406 | 407 | `createSelectorCreator` can be used to make a customized version of `createSelector`. 408 | 409 | The `memoize` argument is a memoization function to replace `defaultMemoize`. 410 | 411 | The `...memoizeOptions` rest parameters are zero or more configuration options to be passsed to `memoizeFunc`. The selectors `resultFunc` is passed as the first argument to `memoize` and the `memoizeOptions` are passed as the second argument onwards: 412 | 413 | ```js 414 | const customSelectorCreator = createSelectorCreator( 415 | customMemoize, // function to be used to memoize resultFunc 416 | option1, // option1 will be passed as second argument to customMemoize 417 | option2, // option2 will be passed as third argument to customMemoize 418 | option3 // option3 will be passed as fourth argument to customMemoize 419 | ); 420 | 421 | const customSelector = customSelectorCreator( 422 | input1, 423 | input2, 424 | resultFunc // resultFunc will be passed as first argument to customMemoize 425 | ); 426 | ``` 427 | 428 | Internally `customSelector` calls the memoize function as follows: 429 | 430 | ```js 431 | customMemoize(resultFunc, option1, option2, option3); 432 | ``` 433 | 434 | Here are some examples of how you might use `createSelectorCreator`: 435 | 436 | #### Customize `equalityCheck` for `defaultMemoize` 437 | 438 | ```js 439 | import { createSelectorCreator, defaultMemoize } from 'reselect'; 440 | import isEqual from 'lodash.isEqual'; 441 | 442 | // create a "selector creator" that uses lodash.isEqual instead of === 443 | const createDeepEqualSelector = createSelectorCreator( 444 | defaultMemoize, 445 | isEqual 446 | ); 447 | 448 | // use the new "selector creator" to create a selector 449 | const mySelector = createDeepEqualSelector( 450 | state => state.values.filter(val => val < 5), 451 | values => values.reduce((acc, val) => acc + val, 0) 452 | ); 453 | ``` 454 | 455 | #### Use memoize function from lodash for an unbounded cache 456 | 457 | ```js 458 | import { createSelectorCreator } from 'reselect'; 459 | import memoize from 'lodash.memoize'; 460 | 461 | let called = 0; 462 | const customSelectorCreator = createSelectorCreator(memoize, JSON.stringify); 463 | const selector = customSelectorCreator( 464 | state => state.a, 465 | state => state.b, 466 | (a, b) => { 467 | called++; 468 | return a + b; 469 | } 470 | ); 471 | ``` 472 | 473 | ### createStructuredSelector({inputSelectors}, selectorCreator = createSelector) 474 | 475 | `createStructuredSelector` is a convenience function that helps with a common pattern when using Reselect. The selector passed to a connect decorator often just takes other selectors and maps them to keys in an object: 476 | 477 | ```js 478 | const mySelectorA = state => state.a; 479 | const mySelectorB = state => state.b; 480 | 481 | const structuredSelector = createSelector( 482 | mySelectorA, 483 | mySelectorB, 484 | mySelectorC, 485 | (a, b, c) => ({ 486 | a, 487 | b, 488 | c 489 | }) 490 | ); 491 | ``` 492 | 493 | `createStructuredSelector` takes an object whose properties are input-selectors and returns a structured selector. The structured selector returns an object with the same keys as the `inputSelectors` argument, but with the selectors replaced with their values. 494 | 495 | ```js 496 | const mySelectorA = state => state.a; 497 | const mySelectorB = state => state.b; 498 | 499 | const structuredSelector = createStructuredSelector({ 500 | x: mySelectorA, 501 | y: mySelectorB 502 | }); 503 | 504 | const result = structuredSelector({a: 1, b: 2}); // will produce {x: 1, y: 2} 505 | ``` 506 | 507 | Structured selectors can be nested: 508 | 509 | ```js 510 | const nestedSelector = createStructuredSelector({ 511 | subA: createStructuredSelector({ 512 | selectorA, 513 | selectorB 514 | }), 515 | subB: createStructuredSelector({ 516 | selectorC, 517 | selectorD 518 | }) 519 | }); 520 | 521 | ``` 522 | 523 | ## FAQ 524 | 525 | ### Q: Why isn't my selector recomputing when the input state changes? 526 | 527 | A: Check that your memoization function is compatible with your state update function (ie the reducer if you are using Redux). For example, a selector created with `createSelector` will not work with a state update function that mutates an existing object instead of creating a new one each time. `createSelector` uses an identity check (`===`) to detect that an input has changed, so mutating an existing object will not trigger the selector to recompute because mutating an object does not change its identity. Note that if you are using Redux, mutating the state object is [almost certainly a mistake](http://rackt.github.io/redux/docs/Troubleshooting.html). 528 | 529 | The following example defines a simple selector that determines if the first todo item in an array of todos has been completed: 530 | 531 | ```js 532 | const isFirstTodoCompleteSelector = createSelector( 533 | state => state.todos[0], 534 | todo => todo && todo.completed 535 | ); 536 | ``` 537 | 538 | The following state update function **will not** work with `isFirstTodoCompleteSelector`: 539 | 540 | ```js 541 | export default function todos(state = initialState, action) { 542 | switch (action.type) { 543 | case COMPLETE_ALL: 544 | const areAllMarked = state.every(todo => todo.completed); 545 | // BAD: mutating an existing object 546 | return state.map(todo => { 547 | todo.completed = !areAllMarked; 548 | return todo; 549 | }); 550 | 551 | default: 552 | return state; 553 | } 554 | } 555 | ``` 556 | 557 | The following state update function **will** work with `isFirstTodoCompleteSelector`: 558 | 559 | ```js 560 | export default function todos(state = initialState, action) { 561 | switch (action.type) { 562 | case COMPLETE_ALL: 563 | const areAllMarked = state.every(todo => todo.completed); 564 | // GOOD: returning a new object each time with Object.assign 565 | return state.map(todo => Object.assign({}, todo, { 566 | completed: !areAllMarked 567 | })); 568 | 569 | default: 570 | return state; 571 | } 572 | } 573 | ``` 574 | 575 | If you are not using Redux and have a requirement to work with mutable data, you can use `createSelectorCreator` to replace the default memoization function and/or use a different equality check function. See [here](#use-memoize-function-from-lodash-for-an-unbounded-cache) and [here](#customize-equalitycheck-for-defaultmemoize) for examples. 576 | 577 | ### Q: Why is my selector recomputing when the input state stays the same? 578 | 579 | A: Check that your memoization function is compatible with your state update function (ie the reducer if you are using Redux). For example, a selector created with `createSelector` that recomputes unexpectedly may be receiving a new object on each update whether the values it contains have changed or not. `createSelector` uses an identity check (`===`) to detect that an input has changed, so returning a new object on each update means that the selector will recompute on each update. 580 | 581 | ```js 582 | import { REMOVE_OLD } from '../constants/ActionTypes'; 583 | 584 | const initialState = [{ 585 | text: 'Use Redux', 586 | completed: false, 587 | id: 0, 588 | timestamp: Date.now() 589 | }]; 590 | 591 | export default function todos(state = initialState, action) { 592 | switch (action.type) { 593 | case REMOVE_OLD: 594 | return state.filter(todo => { 595 | return todo.timestamp + 30 * 24 * 60 * 60 * 1000 > Date.now(); 596 | }); 597 | default: 598 | return state; 599 | } 600 | } 601 | ``` 602 | 603 | The following selector is going to recompute every time REMOVE_OLD is invoked because Array.filter always returns a new object. However, in the majority of cases the REMOVE_OLD action will not change the list of todos so the recomputation is unnecessary. 604 | 605 | ```js 606 | import { createselector } from 'reselect'; 607 | 608 | const todosSelector = state => state.todos; 609 | 610 | export const visibletodosselector = createselector( 611 | todosselector, 612 | (todos) => { 613 | ... 614 | } 615 | ); 616 | ``` 617 | 618 | You can eliminate unnecessary recomputations by returning a new object from the state update function only when a deep equality check has found that the list of todos has actually changed: 619 | 620 | ```js 621 | import { REMOVE_OLD } from '../constants/ActionTypes'; 622 | import isEqual from 'lodash.isEqual'; 623 | 624 | const initialState = [{ 625 | text: 'Use Redux', 626 | completed: false, 627 | id: 0, 628 | timestamp: Date.now() 629 | }]; 630 | 631 | export default function todos(state = initialState, action) { 632 | switch (action.type) { 633 | case REMOVE_OLD: 634 | const updatedState = state.filter(todo => { 635 | return todo.timestamp + 30 * 24 * 60 * 60 * 1000 > Date.now(); 636 | }); 637 | return isEqual(updatedState, state) ? state : updatedState; 638 | default: 639 | return state; 640 | } 641 | } 642 | ``` 643 | 644 | Alternatively, the default `equalityCheck` function in the selector can be replaced by a deep equality check: 645 | 646 | ```js 647 | import { createSelectorCreator, defaultMemoize } from 'reselect'; 648 | import isEqual from 'lodash.isEqual'; 649 | 650 | const todosSelector = state => state.todos; 651 | 652 | // create a "selector creator" that uses lodash.isEqual instead of === 653 | const createDeepEqualSelector = createSelectorCreator( 654 | defaultMemoize, 655 | isEqual 656 | ); 657 | 658 | // use the new "selector creator" to create a selector 659 | const mySelector = createDeepEqualSelector( 660 | todosSelector, 661 | (todos) => { 662 | ... 663 | } 664 | ); 665 | ``` 666 | 667 | Always check that the cost of an alernative `equalityCheck` function or deep equality check in the state update function is not greater than the cost of recomputing every time. If recomputing every time does work out to be the cheaper option, it may be that for this case Reselect is not giving you any benefit over passing a plain `mapStateToProps` function to `connect`. 668 | 669 | ### Q: Can I use Reselect without Redux? 670 | 671 | A: Yes. Reselect has no dependencies on any other package, so although it was designed to be used with Redux it can be used independently. It is currently being used successfully in traditional Flux apps. 672 | 673 | > If you create selectors using `createSelector` make sure the objects in your store are immutable. 674 | > See [here](#createselectorinputselectors-resultfn) 675 | 676 | ### Q: How do I create a selector that takes an argument? 677 | 678 | A: Creating a factory function may be helpful: 679 | 680 | ```js 681 | const expensiveItemSelectorFactory = minValue => { 682 | return createSelector( 683 | shopItemsSelector, 684 | items => items.filter(item => item.value < minValue) 685 | ); 686 | } 687 | 688 | const subtotalSelector = createSelector( 689 | expensiveItemSelectorFactory(200), 690 | items => items.reduce((acc, item) => acc + item.value, 0) 691 | ); 692 | ``` 693 | 694 | ### Q: The default memoization function is no good, can I use a different one? 695 | 696 | A: We think it works great for a lot of use cases, but sure. See [these examples](#customize-equalitycheck-for-defaultmemoize). 697 | 698 | ### Q: How do I test a selector? 699 | 700 | A: For a given input, a selector should always produce the same output. For this reason they are simple to unit test. 701 | 702 | ```js 703 | const selector = createSelector( 704 | state => state.a, 705 | state => state.b, 706 | (a, b) => ({ 707 | c: a * 2, 708 | d: b * 3 709 | }) 710 | ); 711 | 712 | test("selector unit test", function() { 713 | assert.deepEqual(selector({a: 1, b: 2}), {c: 2, d: 6}); 714 | assert.deepEqual(selector({a: 2, b: 3}), {c: 4, d: 9}); 715 | }); 716 | ``` 717 | 718 | It may also be useful to check that the memoization function for a selector works correctly with the state update function (ie the reducer if you are using Redux). Each selector has a `recomputations` method that will return the number of times it has been recomputed: 719 | 720 | ```js 721 | suite('selector', () => { 722 | let state = {a: 1, b: 2}; 723 | 724 | const reducer = (state, action) => ( 725 | { 726 | a: action(state.a), 727 | b: action(state.b) 728 | } 729 | ); 730 | 731 | const selector = createSelector( 732 | state => state.a, 733 | state => state.b, 734 | (a, b) => ({ 735 | c: a * 2, 736 | d: b * 3 737 | }) 738 | ); 739 | 740 | const plusOne = x => x + 1; 741 | const id = x => x; 742 | 743 | test("selector unit test", function() { 744 | state = reducer(state, plusOne); 745 | assert.deepEqual(selector(state), {c: 4, d: 9}); 746 | state = reducer(state, id); 747 | assert.deepEqual(selector(state), {c: 4, d: 9}); 748 | assert.equal(selector.recomputations(), 1); 749 | state = reducer(state, plusOne); 750 | assert.deepEqual(selector(state), {c: 6, d: 12}); 751 | assert.equal(selector.recomputations(), 2); 752 | }); 753 | }); 754 | ``` 755 | 756 | ### Q: How do I use Reselect with Immutable.js? 757 | 758 | A: Selectors created with `createSelector` should work just fine with Immutable.js data structures. 759 | 760 | If your selector is recomputing and you don't think the state has changed, make sure you are aware of which Immutable.js update methods **always** return a new object and which update methods only return a new object **when the collection actually changes**. 761 | 762 | ```js 763 | import Immutable from 'immutable'; 764 | 765 | let myMap = Immutable.Map({ 766 | a: 1, 767 | b: 2, 768 | c: 3 769 | }); 770 | 771 | let newMap = myMap.set('a', 1); // set, merge and others only return a new obj when update changes collection 772 | assert.equal(myMap, newMap); 773 | newMap = myMap.merge({'a', 1}); 774 | assert.equal(myMap, newMap); 775 | newMap = myMap.map(a => a * 1); // map, reduce, filter and others always return a new obj 776 | assert.notEqual(myMap, newMap); 777 | ``` 778 | 779 | If a selector's input is updated by an operation that always returns a new object, it may be performing unnecessary recomputations. See [here](#q-why-is-my-selector-recomputing-when-the-input-state-stays-the-same) for a discussion on the pros and cons of using a deep equality check like `Immmutable.is` to eliminate unnecessary recomputations. 780 | 781 | ## License 782 | 783 | MIT 784 | -------------------------------------------------------------------------------- /reselect-example/node_modules/reselect/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.defaultMemoize = defaultMemoize; 7 | exports.createSelectorCreator = createSelectorCreator; 8 | exports.createSelector = createSelector; 9 | exports.createStructuredSelector = createStructuredSelector; 10 | 11 | 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); } } 12 | 13 | function defaultEqualityCheck(a, b) { 14 | return a === b; 15 | } 16 | 17 | function defaultMemoize(func) { 18 | var equalityCheck = arguments.length <= 1 || arguments[1] === undefined ? defaultEqualityCheck : arguments[1]; 19 | 20 | var lastArgs = null; 21 | var lastResult = null; 22 | return function () { 23 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 24 | args[_key] = arguments[_key]; 25 | } 26 | 27 | if (lastArgs !== null && args.every(function (value, index) { 28 | return equalityCheck(value, lastArgs[index]); 29 | })) { 30 | return lastResult; 31 | } 32 | lastArgs = args; 33 | lastResult = func.apply(undefined, args); 34 | return lastResult; 35 | }; 36 | } 37 | 38 | function createSelectorCreator(memoize) { 39 | for (var _len2 = arguments.length, memoizeOptions = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { 40 | memoizeOptions[_key2 - 1] = arguments[_key2]; 41 | } 42 | 43 | return function () { 44 | for (var _len3 = arguments.length, funcs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 45 | funcs[_key3] = arguments[_key3]; 46 | } 47 | 48 | var recomputations = 0; 49 | var resultFunc = funcs.pop(); 50 | var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs; 51 | 52 | var memoizedResultFunc = memoize.apply(undefined, [function () { 53 | recomputations++; 54 | return resultFunc.apply(undefined, arguments); 55 | }].concat(memoizeOptions)); 56 | 57 | var selector = function selector(state, props) { 58 | for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) { 59 | args[_key4 - 2] = arguments[_key4]; 60 | } 61 | 62 | var params = dependencies.map(function (dependency) { 63 | return dependency.apply(undefined, [state, props].concat(args)); 64 | }); 65 | return memoizedResultFunc.apply(undefined, _toConsumableArray(params)); 66 | }; 67 | 68 | selector.recomputations = function () { 69 | return recomputations; 70 | }; 71 | return selector; 72 | }; 73 | } 74 | 75 | function createSelector() { 76 | return createSelectorCreator(defaultMemoize).apply(undefined, arguments); 77 | } 78 | 79 | function createStructuredSelector(selectors) { 80 | var selectorCreator = arguments.length <= 1 || arguments[1] === undefined ? createSelector : arguments[1]; 81 | 82 | var objectKeys = Object.keys(selectors); 83 | return selectorCreator(objectKeys.map(function (key) { 84 | return selectors[key]; 85 | }), function () { 86 | for (var _len5 = arguments.length, values = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 87 | values[_key5] = arguments[_key5]; 88 | } 89 | 90 | return values.reduce(function (composition, value, index) { 91 | composition[objectKeys[index]] = value; 92 | return composition; 93 | }, {}); 94 | }); 95 | } -------------------------------------------------------------------------------- /reselect-example/node_modules/reselect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reselect", 3 | "version": "1.1.0", 4 | "description": "Selectors for Redux.", 5 | "main": "lib/index.js", 6 | "jsnext:main": "src/index.js", 7 | "files": [ 8 | "lib", 9 | "src", 10 | "README.md", 11 | "LICENSE", 12 | "package.json" 13 | ], 14 | "bugs": { 15 | "url": "https://github.com/faassen/reselect/issues" 16 | }, 17 | "scripts": { 18 | "compile": "babel -d lib/ src/", 19 | "lint": "eslint src test", 20 | "prepublish": "npm run compile", 21 | "test": "NODE_ENV=test mocha --compilers js:babel/register --recursive", 22 | "test:cov": "babel-node ./node_modules/.bin/isparta cover ./node_modules/.bin/_mocha -- --recursive" 23 | }, 24 | "keywords": [ 25 | "react", 26 | "redux" 27 | ], 28 | "author": { 29 | "name": "Martijn Faassen" 30 | }, 31 | "contributors": [ 32 | { 33 | "name": "Martijn Faassen" 34 | }, 35 | { 36 | "name": "Lee Bannard" 37 | }, 38 | { 39 | "name": "Robert Binna" 40 | }, 41 | { 42 | "name": "Philip Spitzlinger" 43 | }, 44 | { 45 | "name": "Alex Guerra" 46 | }, 47 | { 48 | "name": "ryanatkn" 49 | }, 50 | { 51 | "name": "Adam Royle" 52 | }, 53 | { 54 | "name": "Christian Schuhmann" 55 | }, 56 | { 57 | "name": "Jason Huang" 58 | }, 59 | { 60 | "name": "Daniel Barreto" 61 | }, 62 | { 63 | "name": "Mihail Diordiev" 64 | }, 65 | { 66 | "name": "Daniela Borges" 67 | } 68 | ], 69 | "repository": { 70 | "type": "git", 71 | "url": "git+https://github.com/faassen/reselect.git" 72 | }, 73 | "license": "MIT", 74 | "devDependencies": { 75 | "babel": "^5.5.8", 76 | "babel-core": "^5.6.15", 77 | "babel-eslint": "^3.1.15", 78 | "eslint": "^0.23", 79 | "eslint-config-airbnb": "0.0.6", 80 | "chai": "^3.0.0", 81 | "isparta": "^3.0.3", 82 | "lodash.memoize": "^3.0.4", 83 | "mocha": "^2.2.5" 84 | }, 85 | "gitHead": "401f09c888546d4282edc7aae36ef881f393f5a7", 86 | "homepage": "https://github.com/faassen/reselect#readme", 87 | "_id": "reselect@1.1.0", 88 | "_shasum": "8d0ea3a9483ae62b1a0d7d8b4355db4d2ed163fb", 89 | "_from": "reselect@*", 90 | "_npmVersion": "2.14.2", 91 | "_nodeVersion": "0.12.7", 92 | "_npmUser": { 93 | "name": "ellbee", 94 | "email": "l_bannard@yahoo.co.uk" 95 | }, 96 | "dist": { 97 | "shasum": "8d0ea3a9483ae62b1a0d7d8b4355db4d2ed163fb", 98 | "tarball": "http://registry.npmjs.org/reselect/-/reselect-1.1.0.tgz" 99 | }, 100 | "maintainers": [ 101 | { 102 | "name": "faassen", 103 | "email": "faassen@startifact.com" 104 | }, 105 | { 106 | "name": "ellbee", 107 | "email": "l_bannard@yahoo.co.uk" 108 | } 109 | ], 110 | "directories": {}, 111 | "_resolved": "https://registry.npmjs.org/reselect/-/reselect-1.1.0.tgz" 112 | } 113 | -------------------------------------------------------------------------------- /reselect-example/node_modules/reselect/src/index.js: -------------------------------------------------------------------------------- 1 | function defaultEqualityCheck(a, b) { 2 | return a === b; 3 | } 4 | 5 | export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) { 6 | let lastArgs = null; 7 | let lastResult = null; 8 | return (...args) => { 9 | if (lastArgs !== null && 10 | args.every((value, index) => equalityCheck(value, lastArgs[index]))) { 11 | return lastResult; 12 | } 13 | lastArgs = args; 14 | lastResult = func(...args); 15 | return lastResult; 16 | }; 17 | } 18 | 19 | export function createSelectorCreator(memoize, ...memoizeOptions) { 20 | return (...funcs) => { 21 | let recomputations = 0; 22 | const resultFunc = funcs.pop(); 23 | const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs; 24 | 25 | const memoizedResultFunc = memoize( 26 | (...args) => { 27 | recomputations++; 28 | return resultFunc(...args); 29 | }, 30 | ...memoizeOptions 31 | ); 32 | 33 | const selector = (state, props, ...args) => { 34 | const params = dependencies.map( 35 | dependency => dependency(state, props, ...args) 36 | ); 37 | return memoizedResultFunc(...params); 38 | }; 39 | 40 | selector.recomputations = () => recomputations; 41 | return selector; 42 | }; 43 | } 44 | 45 | export function createSelector(...args) { 46 | return createSelectorCreator(defaultMemoize)(...args); 47 | } 48 | 49 | export function createStructuredSelector(selectors, selectorCreator = createSelector) { 50 | let objectKeys = Object.keys(selectors); 51 | return selectorCreator( 52 | objectKeys.map(key => selectors[key]), 53 | (...values) => { 54 | return values.reduce((composition, value, index) => { 55 | composition[objectKeys[index]] = value; 56 | return composition; 57 | }, {}); 58 | } 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /reselect-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reselect-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "example.js", 6 | "scripts": { 7 | "start": "babel-node example.js" 8 | }, 9 | "author": "Neil Fenton (https://www.github.com/neilff)", 10 | "license": "ISC", 11 | "dependencies": { 12 | "reselect": "^1.1.0" 13 | } 14 | } 15 | --------------------------------------------------------------------------------