├── 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 | Go!
55 |
56 |
57 |
58 |
59 | {{ company.name }}
60 |
61 |
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 |
27 |
28 | Increase
31 |
32 |
33 | Decrease
36 |
37 |
38 | Increase If Odd
41 |
42 |
43 | Async Increase
46 |
47 |
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 |
34 |
35 |
40 | Request Posts
41 |
44 |
45 |
46 |
47 |
52 | Request Error
53 |
56 |
57 |
58 |
59 |
No posts loaded.
60 |
61 |
62 | {{ post.data.title }}
63 |
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 | PlayerID
31 |
37 |
38 |
{{ request | json }}
39 |
42 | An error occured: {{ request.player.error | json }}
43 |
44 |
45 |
46 |
51 | Get Player Info
52 |
55 |
56 |
57 |
58 |
63 | Get Player Team Data
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {{ team.teamFullName }} {{ team.seasonId }} {{ team.wins }} {{ team.losses }}
74 |
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 | Increment
21 | Decrement
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 | Submit
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 | x { 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 |
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 | ,
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 | Increase
11 | Decrease
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 |
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 | {
8 | if (onClick) {
9 | onClick();
10 | }
11 | } }>
12 | { children }
13 |
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 |
6 | { children }
7 |
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 | x
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 |
addToast(generateRandomToast())}>
28 | Add Toast to Toaster
29 |
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 |
--------------------------------------------------------------------------------