├── CNAME ├── SUMMARY.md ├── examples ├── universal │ ├── index.js │ ├── .babelrc │ ├── server │ │ ├── index.js │ │ └── server.js │ ├── common │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── counter.js │ │ ├── containers │ │ │ └── App.js │ │ ├── api │ │ │ └── counter.js │ │ ├── store │ │ │ └── configureStore.js │ │ ├── actions │ │ │ └── index.js │ │ └── components │ │ │ └── Counter.js │ ├── client │ │ └── index.js │ ├── webpack.config.js │ └── package.json ├── README.md ├── todos-flow │ ├── .flowconfig │ ├── .gitignore │ ├── src │ │ ├── components │ │ │ ├── App.js │ │ │ ├── Todo.js │ │ │ ├── Footer.js │ │ │ ├── TodoList.js │ │ │ └── Link.js │ │ ├── reducers │ │ │ ├── visibilityFilter.js │ │ │ ├── index.js │ │ │ ├── todos.js │ │ │ └── todos.spec.js │ │ ├── index.js │ │ ├── actions │ │ │ ├── index.js │ │ │ └── index.spec.js │ │ ├── types │ │ │ └── index.js │ │ └── containers │ │ │ ├── FilterLink.js │ │ │ ├── AddTodo.js │ │ │ └── VisibleTodoList.js │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── todomvc │ ├── src │ │ ├── constants │ │ │ ├── TodoFilters.js │ │ │ └── ActionTypes.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── todos.js │ │ ├── index.js │ │ ├── actions │ │ │ ├── index.js │ │ │ └── index.spec.js │ │ ├── components │ │ │ ├── Header.js │ │ │ ├── TodoTextInput.js │ │ │ ├── Header.spec.js │ │ │ ├── TodoItem.js │ │ │ ├── Footer.js │ │ │ ├── MainSection.js │ │ │ └── TodoTextInput.spec.js │ │ └── containers │ │ │ └── App.js │ ├── .gitignore │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── real-world │ ├── src │ │ ├── containers │ │ │ ├── Root.js │ │ │ ├── DevTools.js │ │ │ ├── Root.prod.js │ │ │ ├── Root.dev.js │ │ │ └── App.js │ │ ├── store │ │ │ ├── configureStore.js │ │ │ ├── configureStore.prod.js │ │ │ └── configureStore.dev.js │ │ ├── routes.js │ │ ├── index.js │ │ ├── components │ │ │ ├── User.js │ │ │ ├── Repo.js │ │ │ ├── List.js │ │ │ └── Explore.js │ │ └── reducers │ │ │ ├── index.js │ │ │ └── paginate.js │ ├── .gitignore │ ├── public │ │ └── index.html │ ├── package.json │ └── README.md ├── async │ ├── .gitignore │ ├── src │ │ ├── components │ │ │ ├── Posts.js │ │ │ └── Picker.js │ │ ├── index.js │ │ ├── actions │ │ │ └── index.js │ │ ├── reducers │ │ │ └── index.js │ │ └── containers │ │ │ └── App.js │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── counter │ ├── .gitignore │ ├── src │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── index.spec.js │ │ ├── index.js │ │ └── components │ │ │ ├── Counter.js │ │ │ └── Counter.spec.js │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── todos │ ├── .gitignore │ ├── src │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── visibilityFilter.js │ │ │ ├── todos.js │ │ │ └── todos.spec.js │ │ ├── components │ │ │ ├── App.js │ │ │ ├── Todo.js │ │ │ ├── Footer.js │ │ │ ├── Link.js │ │ │ └── TodoList.js │ │ ├── actions │ │ │ ├── index.js │ │ │ └── index.spec.js │ │ ├── index.js │ │ └── containers │ │ │ ├── FilterLink.js │ │ │ ├── AddTodo.js │ │ │ └── VisibleTodoList.js │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── tree-view │ ├── .gitignore │ ├── src │ │ ├── generateTree.js │ │ ├── index.js │ │ ├── actions │ │ │ └── index.js │ │ ├── reducers │ │ │ └── index.js │ │ └── containers │ │ │ └── Node.js │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── shopping-cart │ ├── .gitignore │ ├── src │ │ ├── api │ │ │ ├── products.json │ │ │ └── shop.js │ │ ├── constants │ │ │ └── ActionTypes.js │ │ ├── containers │ │ │ ├── App.js │ │ │ ├── CartContainer.js │ │ │ └── ProductsContainer.js │ │ ├── components │ │ │ ├── ProductsList.js │ │ │ ├── Product.js │ │ │ ├── ProductItem.js │ │ │ ├── Product.spec.js │ │ │ ├── ProductsList.spec.js │ │ │ ├── Cart.js │ │ │ ├── ProductItem.spec.js │ │ │ └── Cart.spec.js │ │ ├── index.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── cart.spec.js │ │ │ ├── products.spec.js │ │ │ ├── products.js │ │ │ ├── cart.js │ │ │ └── index.spec.js │ │ └── actions │ │ │ └── index.js │ ├── public │ │ └── index.html │ ├── package.json │ └── README.md ├── todos-with-undo │ ├── .gitignore │ ├── src │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── visibilityFilter.js │ │ │ └── todos.js │ │ ├── actions │ │ │ └── index.js │ │ ├── index.js │ │ ├── components │ │ │ ├── App.js │ │ │ ├── Todo.js │ │ │ ├── Footer.js │ │ │ ├── Link.js │ │ │ └── TodoList.js │ │ └── containers │ │ │ ├── FilterLink.js │ │ │ ├── AddTodo.js │ │ │ ├── UndoRedo.js │ │ │ └── VisibleTodoList.js │ ├── package.json │ ├── public │ │ └── index.html │ └── README.md ├── buildAll.js ├── testAll.js └── counter-vanilla │ └── index.html ├── test ├── .eslintrc ├── helpers │ ├── middleware.js │ ├── actionTypes.js │ ├── actionCreators.js │ └── reducers.js ├── typescript.spec.js ├── typescript │ ├── dispatch.ts │ ├── actions.ts │ ├── reducers.ts │ ├── compose.ts │ ├── store.ts │ ├── actionCreators.ts │ └── middleware.ts ├── utils │ └── warning.spec.js └── compose.spec.js ├── logo ├── logo.png ├── favicon.ico ├── apple-touch-icon.png ├── logo-title-dark.png ├── logo-title-light.png ├── logo.svg └── README.md ├── .gitignore ├── .eslintignore ├── docs ├── advanced │ ├── NextSteps.md │ ├── README.md │ └── AsyncFlow.md ├── introduction │ ├── README.md │ └── Motivation.md ├── Feedback.md ├── basics │ └── README.md ├── recipes │ ├── README.md │ ├── MigratingToRedux.md │ └── IsolatingSubapps.md └── api │ ├── README.md │ └── compose.md ├── CHANGELOG.md ├── .eslintrc ├── .editorconfig ├── .travis.yml ├── src ├── utils │ └── warning.js ├── compose.js ├── index.js ├── applyMiddleware.js └── bindActionCreators.js ├── book.json ├── .github └── ISSUE_TEMPLATE.md ├── webpack.config.js ├── PATRONS.md ├── LICENSE.md ├── .babelrc └── flow-typed └── redux.js /CNAME: -------------------------------------------------------------------------------- 1 | redux.js.org 2 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | docs/README.md -------------------------------------------------------------------------------- /examples/universal/index.js: -------------------------------------------------------------------------------- 1 | require('./client') 2 | -------------------------------------------------------------------------------- /examples/universal/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ["es2015", "react"] 3 | } -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markerikson/redux/HEAD/logo/logo.png -------------------------------------------------------------------------------- /examples/universal/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | require('./server') 3 | -------------------------------------------------------------------------------- /logo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markerikson/redux/HEAD/logo/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | dist 5 | lib 6 | es 7 | coverage 8 | _book 9 | -------------------------------------------------------------------------------- /logo/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markerikson/redux/HEAD/logo/apple-touch-icon.png -------------------------------------------------------------------------------- /logo/logo-title-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markerikson/redux/HEAD/logo/logo-title-dark.png -------------------------------------------------------------------------------- /logo/logo-title-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markerikson/redux/HEAD/logo/logo-title-light.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/** 2 | **/examples/** 3 | **/node_modules/** 4 | **/server.js 5 | **/webpack.config*.js 6 | -------------------------------------------------------------------------------- /docs/advanced/NextSteps.md: -------------------------------------------------------------------------------- 1 | # Next Steps 2 | 3 | Sorry, but we're still writing this doc. 4 | Stay tuned, it will appear in a day or two. 5 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Read the descriptions for every example on the [Examples](../docs/introduction/Examples.md) documentation page. 4 | -------------------------------------------------------------------------------- /examples/todos-flow/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/fbjs 3 | 4 | [include] 5 | 6 | [libs] 7 | ../../flow-typed 8 | 9 | [options] 10 | -------------------------------------------------------------------------------- /examples/todomvc/src/constants/TodoFilters.js: -------------------------------------------------------------------------------- 1 | export const SHOW_ALL = 'show_all' 2 | export const SHOW_COMPLETED = 'show_completed' 3 | export const SHOW_ACTIVE = 'show_active' 4 | -------------------------------------------------------------------------------- /examples/real-world/src/containers/Root.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./Root.prod') 3 | } else { 4 | module.exports = require('./Root.dev') 5 | } 6 | -------------------------------------------------------------------------------- /examples/todomvc/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import todos from './todos' 3 | 4 | const rootReducer = combineReducers({ 5 | todos 6 | }) 7 | 8 | export default rootReducer 9 | -------------------------------------------------------------------------------- /test/helpers/middleware.js: -------------------------------------------------------------------------------- 1 | export function thunk({ dispatch, getState }) { 2 | return next => action => 3 | typeof action === 'function' ? 4 | action(dispatch, getState) : 5 | next(action) 6 | } 7 | -------------------------------------------------------------------------------- /test/helpers/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_TODO = 'ADD_TODO' 2 | export const DISPATCH_IN_MIDDLE = 'DISPATCH_IN_MIDDLE' 3 | export const THROW_ERROR = 'THROW_ERROR' 4 | export const UNKNOWN_ACTION = 'UNKNOWN_ACTION' 5 | -------------------------------------------------------------------------------- /docs/introduction/README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | * [Motivation](Motivation.md) 4 | * [Three Principles](ThreePrinciples.md) 5 | * [Prior Art](PriorArt.md) 6 | * [Ecosystem](Ecosystem.md) 7 | * [Examples](Examples.md) 8 | -------------------------------------------------------------------------------- /examples/real-world/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.prod') 3 | } else { 4 | module.exports = require('./configureStore.dev') 5 | } 6 | -------------------------------------------------------------------------------- /examples/universal/common/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import counter from './counter' 3 | 4 | const rootReducer = combineReducers({ 5 | counter 6 | }) 7 | 8 | export default rootReducer 9 | -------------------------------------------------------------------------------- /examples/async/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/counter/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/todomvc/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/todos/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/real-world/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/todos-flow/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/tree-view/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/shopping-cart/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /examples/todos-with-undo/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/reactjs/redux/releases) page. 5 | -------------------------------------------------------------------------------- /examples/counter/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | export default (state = 0, action) => { 2 | switch (action.type) { 3 | case 'INCREMENT': 4 | return state + 1 5 | case 'DECREMENT': 6 | return state - 1 7 | default: 8 | return state 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/api/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": 1, "title": "iPad 4 Mini", "price": 500.01, "inventory": 2}, 3 | {"id": 2, "title": "H&M T-Shirt White", "price": 10.99, "inventory": 10}, 4 | {"id": 3, "title": "Charli XCX - Sucker CD", "price": 19.99, "inventory": 5} 5 | ] 6 | -------------------------------------------------------------------------------- /examples/todos/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import todos from './todos' 3 | import visibilityFilter from './visibilityFilter' 4 | 5 | const todoApp = combineReducers({ 6 | todos, 7 | visibilityFilter 8 | }) 9 | 10 | export default todoApp 11 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import todos from './todos' 3 | import visibilityFilter from './visibilityFilter' 4 | 5 | const todoApp = combineReducers({ 6 | todos, 7 | visibilityFilter 8 | }) 9 | 10 | export default todoApp 11 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_TO_CART = 'ADD_TO_CART' 2 | export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST' 3 | export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS' 4 | export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE' 5 | export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS' 6 | -------------------------------------------------------------------------------- /examples/todos/src/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | const visibilityFilter = (state = 'SHOW_ALL', action) => { 2 | switch (action.type) { 3 | case 'SET_VISIBILITY_FILTER': 4 | return action.filter 5 | default: 6 | return state 7 | } 8 | } 9 | 10 | export default visibilityFilter 11 | -------------------------------------------------------------------------------- /examples/todomvc/src/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_TODO = 'ADD_TODO' 2 | export const DELETE_TODO = 'DELETE_TODO' 3 | export const EDIT_TODO = 'EDIT_TODO' 4 | export const COMPLETE_TODO = 'COMPLETE_TODO' 5 | export const COMPLETE_ALL = 'COMPLETE_ALL' 6 | export const CLEAR_COMPLETED = 'CLEAR_COMPLETED' 7 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | const visibilityFilter = (state = 'SHOW_ALL', action) => { 2 | switch (action.type) { 3 | case 'SET_VISIBILITY_FILTER': 4 | return action.filter 5 | default: 6 | return state 7 | } 8 | } 9 | 10 | export default visibilityFilter 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "rackt", 3 | "rules": { 4 | "valid-jsdoc": 2, 5 | // Disable until Flow supports let and const 6 | "no-var": 0, 7 | "react/jsx-uses-react": 1, 8 | "react/jsx-no-undef": 2, 9 | "react/wrap-multilines": 2 10 | }, 11 | "plugins": [ 12 | "react" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /examples/async/src/components/Posts.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Posts = ({posts}) => ( 4 | 9 | ) 10 | 11 | Posts.propTypes = { 12 | posts: PropTypes.array.isRequired 13 | } 14 | 15 | export default Posts 16 | -------------------------------------------------------------------------------- /test/typescript.spec.js: -------------------------------------------------------------------------------- 1 | import * as tt from 'typescript-definition-tester' 2 | 3 | 4 | describe('TypeScript definitions', function () { 5 | it('should compile against index.d.ts', (done) => { 6 | tt.compileDirectory( 7 | __dirname + '/typescript', 8 | fileName => fileName.match(/\.ts$/), 9 | () => done() 10 | ) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /examples/todos/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Footer from './Footer' 3 | import AddTodo from '../containers/AddTodo' 4 | import VisibleTodoList from '../containers/VisibleTodoList' 5 | 6 | const App = () => ( 7 |
8 | 9 | 10 |
11 |
12 | ) 13 | 14 | export default App 15 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/api/shop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mocking client-server processing 3 | */ 4 | import _products from './products.json' 5 | 6 | const TIMEOUT = 100 7 | 8 | export default { 9 | getProducts: (cb, timeout) => setTimeout(() => cb(_products), timeout || TIMEOUT), 10 | buyProducts: (payload, cb, timeout) => setTimeout(() => cb(), timeout || TIMEOUT) 11 | } 12 | -------------------------------------------------------------------------------- /examples/todos/src/actions/index.js: -------------------------------------------------------------------------------- 1 | let nextTodoId = 0 2 | export const addTodo = (text) => ({ 3 | type: 'ADD_TODO', 4 | id: nextTodoId++, 5 | text 6 | }) 7 | 8 | export const setVisibilityFilter = (filter) => ({ 9 | type: 'SET_VISIBILITY_FILTER', 10 | filter 11 | }) 12 | 13 | export const toggleTodo = (id) => ({ 14 | type: 'TOGGLE_TODO', 15 | id 16 | }) 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/actions/index.js: -------------------------------------------------------------------------------- 1 | let nextTodoId = 0 2 | export const addTodo = (text) => ({ 3 | type: 'ADD_TODO', 4 | id: nextTodoId++, 5 | text 6 | }) 7 | 8 | export const setVisibilityFilter = (filter) => ({ 9 | type: 'SET_VISIBILITY_FILTER', 10 | filter 11 | }) 12 | 13 | export const toggleTodo = (id) => ({ 14 | type: 'TOGGLE_TODO', 15 | id 16 | }) 17 | -------------------------------------------------------------------------------- /examples/todos-flow/src/components/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import Footer from './Footer' 4 | import AddTodo from '../containers/AddTodo' 5 | import VisibleTodoList from '../containers/VisibleTodoList' 6 | 7 | const App = () => ( 8 |
9 | 10 | 11 |
12 |
13 | ) 14 | 15 | export default App 16 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ProductsContainer from './ProductsContainer' 3 | import CartContainer from './CartContainer' 4 | 5 | const App = () => ( 6 |
7 |

Shopping Cart Example

8 |
9 | 10 |
11 | 12 |
13 | ) 14 | 15 | export default App 16 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/ProductsList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const ProductsList = ({ title, children }) => ( 4 |
5 |

{title}

6 |
{children}
7 |
8 | ) 9 | 10 | ProductsList.propTypes = { 11 | children: PropTypes.node, 12 | title: PropTypes.string.isRequired 13 | } 14 | 15 | export default ProductsList 16 | -------------------------------------------------------------------------------- /examples/real-world/src/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import api from '../middleware/api' 4 | import rootReducer from '../reducers' 5 | 6 | const configureStore = preloadedState => createStore( 7 | rootReducer, 8 | preloadedState, 9 | applyMiddleware(thunk, api) 10 | ) 11 | 12 | export default configureStore 13 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/Product.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Product = ({ price, quantity, title }) => ( 4 |
5 | {title} - ${price}{quantity ? ` x ${quantity}` : null} 6 |
7 | ) 8 | 9 | Product.propTypes = { 10 | price: PropTypes.number, 11 | quantity: PropTypes.number, 12 | title: PropTypes.string 13 | } 14 | 15 | export default Product 16 | -------------------------------------------------------------------------------- /examples/real-world/src/containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createDevTools } from 'redux-devtools' 3 | import LogMonitor from 'redux-devtools-log-monitor' 4 | import DockMonitor from 'redux-devtools-dock-monitor' 5 | 6 | export default createDevTools( 7 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /examples/todos/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { createStore } from 'redux' 4 | import { Provider } from 'react-redux' 5 | import App from './components/App' 6 | import reducer from './reducers' 7 | 8 | const store = createStore(reducer) 9 | 10 | render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ) 16 | -------------------------------------------------------------------------------- /examples/todos-flow/src/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { VisibilityFilter, Action } from '../types' 3 | 4 | const visibilityFilter = (state: VisibilityFilter = 'SHOW_ALL', action: Action): VisibilityFilter => { 5 | switch (action.type) { 6 | case 'SET_VISIBILITY_FILTER': 7 | return action.filter 8 | default: 9 | return state 10 | } 11 | } 12 | 13 | export default visibilityFilter 14 | -------------------------------------------------------------------------------- /examples/todos-flow/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import todos from './todos' 3 | import visibilityFilter from './visibilityFilter' 4 | import type { State, Action } from '../types' 5 | 6 | export default function todoApp(state: ?State, action: Action): State { 7 | const s = state || {} 8 | return { 9 | todos: todos(s.todos, action), 10 | visibilityFilter: visibilityFilter(s.visibilityFilter, action) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { createStore } from 'redux' 4 | import { Provider } from 'react-redux' 5 | import App from './components/App' 6 | import reducer from './reducers' 7 | 8 | const store = createStore(reducer) 9 | 10 | render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ) 16 | -------------------------------------------------------------------------------- /examples/real-world/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route } from 'react-router' 3 | import App from './containers/App' 4 | import UserPage from './containers/UserPage' 5 | import RepoPage from './containers/RepoPage' 6 | 7 | export default 8 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /docs/Feedback.md: -------------------------------------------------------------------------------- 1 | # Feedback 2 | 3 | We appreciate feedback from the community. You can post feature requests and bug reports on [Product Pains](https://productpains.com/product/redux). 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Footer from './Footer' 3 | import AddTodo from '../containers/AddTodo' 4 | import VisibleTodoList from '../containers/VisibleTodoList' 5 | import UndoRedo from '../containers/UndoRedo' 6 | 7 | const App = () => ( 8 |
9 | 10 | 11 |
12 | 13 |
14 | ) 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /examples/todomvc/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { createStore } from 'redux' 4 | import { Provider } from 'react-redux' 5 | import App from './containers/App' 6 | import reducer from './reducers' 7 | import 'todomvc-app-css/index.css' 8 | 9 | const store = createStore(reducer) 10 | 11 | render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ) 17 | -------------------------------------------------------------------------------- /examples/universal/common/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { SET_COUNTER, INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions' 2 | 3 | const counter = (state = 0, action) => { 4 | switch (action.type) { 5 | case SET_COUNTER: 6 | return action.payload 7 | case INCREMENT_COUNTER: 8 | return state + 1 9 | case DECREMENT_COUNTER: 10 | return state - 1 11 | default: 12 | return state 13 | } 14 | } 15 | 16 | export default counter 17 | -------------------------------------------------------------------------------- /docs/advanced/README.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | In the [basics walkthrough](../basics/README.md), we explored how to structure a simple Redux application. In this walkthrough, we will explore how AJAX and routing fit into the picture. 4 | 5 | * [Async Actions](AsyncActions.md) 6 | * [Async Flow](AsyncFlow.md) 7 | * [Middleware](Middleware.md) 8 | * [Usage with React Router](UsageWithReactRouter.md) 9 | * [Example: Reddit API](ExampleRedditAPI.md) 10 | * [Next Steps](NextSteps.md) 11 | -------------------------------------------------------------------------------- /examples/tree-view/src/generateTree.js: -------------------------------------------------------------------------------- 1 | export default function generateTree() { 2 | let tree = { 3 | 0: { 4 | id: 0, 5 | counter: 0, 6 | childIds: [] 7 | } 8 | } 9 | 10 | for (let i = 1; i < 1000; i++) { 11 | let parentId = Math.floor(Math.pow(Math.random(), 2) * i) 12 | tree[i] = { 13 | id: i, 14 | counter: 0, 15 | childIds: [] 16 | } 17 | tree[parentId].childIds.push(i) 18 | } 19 | 20 | return tree 21 | } 22 | -------------------------------------------------------------------------------- /examples/todos-flow/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import { createStore } from 'redux' 5 | import { Provider } from 'react-redux' 6 | import App from './components/App' 7 | import reducer from './reducers' 8 | import type { Store } from './types' 9 | 10 | const store: Store = createStore(reducer) 11 | 12 | render( 13 | 14 | 15 | , 16 | document.getElementById('root') 17 | ) 18 | -------------------------------------------------------------------------------- /examples/real-world/src/containers/Root.prod.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { Provider } from 'react-redux' 3 | import routes from '../routes' 4 | import { Router } from 'react-router' 5 | 6 | const Root = ({ store, history }) => ( 7 | 8 | 9 | 10 | ) 11 | 12 | Root.propTypes = { 13 | store: PropTypes.object.isRequired, 14 | history: PropTypes.object.isRequired 15 | } 16 | -------------------------------------------------------------------------------- /examples/todos-flow/src/components/Todo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import type { Text } from '../types' 4 | 5 | export type Props = { 6 | onClick: () => void, 7 | completed: boolean, 8 | text: Text 9 | }; 10 | 11 | const Todo = ({ onClick, completed, text }: Props) => ( 12 |
  • 18 | {text} 19 |
  • 20 | ) 21 | 22 | export default Todo 23 | -------------------------------------------------------------------------------- /examples/todos/src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Todo = ({ onClick, completed, text }) => ( 4 |
  • 10 | {text} 11 |
  • 12 | ) 13 | 14 | Todo.propTypes = { 15 | onClick: PropTypes.func.isRequired, 16 | completed: PropTypes.bool.isRequired, 17 | text: PropTypes.string.isRequired 18 | } 19 | 20 | export default Todo 21 | -------------------------------------------------------------------------------- /examples/universal/common/containers/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux' 2 | import { connect } from 'react-redux' 3 | import Counter from '../components/Counter' 4 | import * as CounterActions from '../actions' 5 | 6 | const mapStateToProps = (state) => ({ 7 | counter: state.counter 8 | }) 9 | 10 | function mapDispatchToProps(dispatch) { 11 | return bindActionCreators(CounterActions, dispatch) 12 | } 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Counter) 15 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Todo = ({ onClick, completed, text }) => ( 4 |
  • 10 | {text} 11 |
  • 12 | ) 13 | 14 | Todo.propTypes = { 15 | onClick: PropTypes.func.isRequired, 16 | completed: PropTypes.bool.isRequired, 17 | text: PropTypes.string.isRequired 18 | } 19 | 20 | export default Todo 21 | -------------------------------------------------------------------------------- /examples/todomvc/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | 3 | export const addTodo = text => ({ type: types.ADD_TODO, text }) 4 | export const deleteTodo = id => ({ type: types.DELETE_TODO, id }) 5 | export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text }) 6 | export const completeTodo = id => ({ type: types.COMPLETE_TODO, id }) 7 | export const completeAll = () => ({ type: types.COMPLETE_ALL }) 8 | export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED }) 9 | -------------------------------------------------------------------------------- /examples/real-world/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { browserHistory } from 'react-router' 4 | import { syncHistoryWithStore } from 'react-router-redux' 5 | import Root from './containers/Root' 6 | import configureStore from './store/configureStore' 7 | 8 | const store = configureStore() 9 | const history = syncHistoryWithStore(browserHistory, store) 10 | 11 | render( 12 | , 13 | document.getElementById('root') 14 | ) 15 | -------------------------------------------------------------------------------- /examples/todos/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FilterLink from '../containers/FilterLink' 3 | 4 | const Footer = () => ( 5 |

    6 | Show: 7 | {" "} 8 | 9 | All 10 | 11 | {", "} 12 | 13 | Active 14 | 15 | {", "} 16 | 17 | Completed 18 | 19 |

    20 | ) 21 | 22 | export default Footer 23 | -------------------------------------------------------------------------------- /examples/tree-view/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { createStore } from 'redux' 4 | import { Provider } from 'react-redux' 5 | import reducer from './reducers' 6 | import generateTree from './generateTree' 7 | import Node from './containers/Node' 8 | 9 | const tree = generateTree() 10 | const store = createStore(reducer, tree) 11 | 12 | render( 13 | 14 | 15 | , 16 | document.getElementById('root') 17 | ) 18 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FilterLink from '../containers/FilterLink' 3 | 4 | const Footer = () => ( 5 |

    6 | Show: 7 | {" "} 8 | 9 | All 10 | 11 | {", "} 12 | 13 | Active 14 | 15 | {", "} 16 | 17 | Completed 18 | 19 |

    20 | ) 21 | 22 | export default Footer 23 | -------------------------------------------------------------------------------- /examples/universal/common/api/counter.js: -------------------------------------------------------------------------------- 1 | const getRandomInt = (min, max) => ( 2 | Math.floor(Math.random() * (max - min)) + min 3 | ) 4 | 5 | export const fetchCounter = (callback) => { 6 | // Rather than immediately returning, we delay our code with a timeout to simulate asynchronous behavior 7 | setTimeout(() => { 8 | callback(getRandomInt(1, 100)) 9 | }, 500) 10 | 11 | // In the case of a real world API call, you'll normally run into a Promise like this: 12 | // API.getUser().then(user => callback(user)) 13 | } 14 | -------------------------------------------------------------------------------- /examples/todos-flow/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import FilterLink from '../containers/FilterLink' 4 | 5 | const Footer = () => ( 6 |

    7 | Show: 8 | {" "} 9 | 10 | All 11 | 12 | {", "} 13 | 14 | Active 15 | 16 | {", "} 17 | 18 | Completed 19 | 20 |

    21 | ) 22 | 23 | export default Footer 24 | -------------------------------------------------------------------------------- /docs/basics/README.md: -------------------------------------------------------------------------------- 1 | # Basics 2 | 3 | Don't be fooled by all the fancy talk about reducers, middleware, store enhancers—Redux is incredibly simple. If you've ever built a Flux application, you will feel right at home. If you're new to Flux, it's easy too! 4 | 5 | In this guide, we'll walk through the process of creating a simple Todo app. 6 | 7 | * [Actions](Actions.md) 8 | * [Reducers](Reducers.md) 9 | * [Store](Store.md) 10 | * [Data Flow](DataFlow.md) 11 | * [Usage with React](UsageWithReact.md) 12 | * [Example: Todo List](ExampleTodoList.md) 13 | -------------------------------------------------------------------------------- /examples/todos-flow/src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import Todo from './Todo' 4 | import type { Todos, Id } from '../types' 5 | 6 | export type Props = { 7 | todos: Todos, 8 | onTodoClick: (id: Id) => void 9 | }; 10 | 11 | const TodoList = ({ todos, onTodoClick }: Props) => ( 12 |
      13 | {todos.map(todo => 14 | onTodoClick(todo.id)} 18 | /> 19 | )} 20 |
    21 | ) 22 | 23 | export default TodoList 24 | -------------------------------------------------------------------------------- /examples/universal/client/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import { Provider } from 'react-redux' 5 | import configureStore from '../common/store/configureStore' 6 | import App from '../common/containers/App' 7 | 8 | const preloadedState = window.__PRELOADED_STATE__ 9 | const store = configureStore(preloadedState) 10 | const rootElement = document.getElementById('app') 11 | 12 | render( 13 | 14 | 15 | , 16 | rootElement 17 | ) 18 | -------------------------------------------------------------------------------- /test/typescript/dispatch.ts: -------------------------------------------------------------------------------- 1 | import {Dispatch, Action} from "../../index.d.ts"; 2 | 3 | 4 | declare const dispatch: Dispatch; 5 | 6 | const dispatchResult: Action = dispatch({type: 'TYPE'}); 7 | 8 | // thunk 9 | declare module "../../index.d.ts" { 10 | export interface Dispatch { 11 | (asyncAction: (dispatch: Dispatch, getState: () => S) => R): R; 12 | } 13 | } 14 | 15 | const dispatchThunkResult: number = dispatch(() => 42); 16 | const dispatchedTimerId: number = dispatch(d => setTimeout(() => d({type: 'TYPE'}), 1000)); 17 | -------------------------------------------------------------------------------- /examples/todos-flow/src/components/Link.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | 4 | export type Props = { 5 | active: boolean, 6 | children?: React$Element, 7 | onClick: () => void 8 | }; 9 | 10 | const Link = ({ active, children, onClick }: Props) => { 11 | if (active) { 12 | return {children} 13 | } 14 | 15 | return ( 16 | { 18 | e.preventDefault() 19 | onClick() 20 | }} 21 | > 22 | {children} 23 | 24 | ) 25 | } 26 | 27 | export default Link 28 | -------------------------------------------------------------------------------- /examples/todos/src/containers/FilterLink.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { setVisibilityFilter } from '../actions' 3 | import Link from '../components/Link' 4 | 5 | const mapStateToProps = (state, ownProps) => ({ 6 | active: ownProps.filter === state.visibilityFilter 7 | }) 8 | 9 | const mapDispatchToProps = (dispatch, ownProps) => ({ 10 | onClick: () => { 11 | dispatch(setVisibilityFilter(ownProps.filter)) 12 | } 13 | }) 14 | 15 | const FilterLink = connect( 16 | mapStateToProps, 17 | mapDispatchToProps 18 | )(Link) 19 | 20 | export default FilterLink 21 | -------------------------------------------------------------------------------- /examples/todos/src/components/Link.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Link = ({ active, children, onClick }) => { 4 | if (active) { 5 | return {children} 6 | } 7 | 8 | return ( 9 | { 11 | e.preventDefault() 12 | onClick() 13 | }} 14 | > 15 | {children} 16 | 17 | ) 18 | } 19 | 20 | Link.propTypes = { 21 | active: PropTypes.bool.isRequired, 22 | children: PropTypes.node.isRequired, 23 | onClick: PropTypes.func.isRequired 24 | } 25 | 26 | export default Link 27 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/containers/FilterLink.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { setVisibilityFilter } from '../actions' 3 | import Link from '../components/Link' 4 | 5 | const mapStateToProps = (state, ownProps) => ({ 6 | active: ownProps.filter === state.visibilityFilter 7 | }) 8 | 9 | const mapDispatchToProps = (dispatch, ownProps) => ({ 10 | onClick: () => { 11 | dispatch(setVisibilityFilter(ownProps.filter)) 12 | } 13 | }) 14 | 15 | const FilterLink = connect( 16 | mapStateToProps, 17 | mapDispatchToProps 18 | )(Link) 19 | 20 | export default FilterLink 21 | -------------------------------------------------------------------------------- /examples/todos-flow/src/actions/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Id, Text, VisibilityFilter, Action } from '../types' 3 | 4 | let nextTodoId: Id = 0 5 | 6 | export const addTodo = (text: Text): Action => { 7 | return { 8 | type: 'ADD_TODO', 9 | id: nextTodoId++, 10 | text 11 | } 12 | } 13 | 14 | export const setVisibilityFilter = (filter: VisibilityFilter): Action => { 15 | return { 16 | type: 'SET_VISIBILITY_FILTER', 17 | filter 18 | } 19 | } 20 | 21 | export const toggleTodo = (id: Id): Action => { 22 | return { 23 | type: 'TOGGLE_TODO', 24 | id 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/components/Link.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Link = ({ active, children, onClick }) => { 4 | if (active) { 5 | return {children} 6 | } 7 | 8 | return ( 9 | { 11 | e.preventDefault() 12 | onClick() 13 | }} 14 | > 15 | {children} 16 | 17 | ) 18 | } 19 | 20 | Link.propTypes = { 21 | active: PropTypes.bool.isRequired, 22 | children: PropTypes.node.isRequired, 23 | onClick: PropTypes.func.isRequired 24 | } 25 | 26 | export default Link 27 | -------------------------------------------------------------------------------- /examples/real-world/src/containers/Root.dev.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Provider } from 'react-redux' 3 | import routes from '../routes' 4 | import DevTools from './DevTools' 5 | import { Router } from 'react-router' 6 | 7 | const Root = ({ store, history }) => ( 8 | 9 |
    10 | 11 | 12 |
    13 |
    14 | ) 15 | 16 | Root.propTypes = { 17 | store: PropTypes.object.isRequired, 18 | history: PropTypes.object.isRequired 19 | } 20 | 21 | export default Root 22 | -------------------------------------------------------------------------------- /examples/counter/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { createStore } from 'redux' 4 | import Counter from './components/Counter' 5 | import counter from './reducers' 6 | 7 | const store = createStore(counter) 8 | const rootEl = document.getElementById('root') 9 | 10 | const render = () => ReactDOM.render( 11 | store.dispatch({ type: 'INCREMENT' })} 14 | onDecrement={() => store.dispatch({ type: 'DECREMENT' })} 15 | />, 16 | rootEl 17 | ) 18 | 19 | render() 20 | store.subscribe(render) 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | - "6" 6 | script: 7 | - npm run check:src 8 | - npm run build 9 | - npm run check:examples 10 | branches: 11 | only: 12 | - master 13 | cache: 14 | directories: 15 | - $HOME/.npm 16 | - examples/async/node_modules 17 | - examples/counter/node_modules 18 | - examples/real-world/node_modules 19 | - examples/shopping-cart/node_modules 20 | - examples/todomvc/node_modules 21 | - examples/todos/node_modules 22 | - examples/todos-with-undo/node_modules 23 | - examples/tree-view/node_modules 24 | - examples/universal/node_modules 25 | -------------------------------------------------------------------------------- /examples/todos-with-undo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todos-with-undo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0" 7 | }, 8 | "dependencies": { 9 | "react": "^15.3.0", 10 | "react-dom": "^15.3.0", 11 | "react-redux": "^4.4.5", 12 | "redux": "^3.5.2", 13 | "redux-undo": "^1.0.0-beta9" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "./node_modules/react-scripts/config/eslint.js" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/async/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0", 7 | "redux-logger": "^2.6.1" 8 | }, 9 | "dependencies": { 10 | "react": "^15.3.0", 11 | "react-dom": "^15.3.0", 12 | "react-redux": "^4.4.5", 13 | "redux": "^3.5.2", 14 | "redux-thunk": "^2.1.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "./node_modules/react-scripts/config/eslint.js" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/counter/src/reducers/index.spec.js: -------------------------------------------------------------------------------- 1 | import counter from './index' 2 | 3 | describe('reducers', () => { 4 | describe('counter', () => { 5 | it('should provide the initial state', () => { 6 | expect(counter(undefined, {})).toBe(0) 7 | }) 8 | 9 | it('should handle INCREMENT action', () => { 10 | expect(counter(1, { type: 'INCREMENT' })).toBe(2) 11 | }) 12 | 13 | it('should handle DECREMENT action', () => { 14 | expect(counter(1, { type: 'DECREMENT' })).toBe(0) 15 | }) 16 | 17 | it('should ignore unknown actions', () => { 18 | expect(counter(1, { type: 'unknown' })).toBe(1) 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /docs/recipes/README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | These are some use cases and code snippets to get you started with Redux in a real app. They assume you understand the topics in [basic](../basics/README.md) and [advanced](../advanced/README.md) tutorials. 4 | 5 | * [Migrating to Redux](MigratingToRedux.md) 6 | * [Using Object Spread Operator](UsingObjectSpreadOperator.md) 7 | * [Reducing Boilerplate](ReducingBoilerplate.md) 8 | * [Server Rendering](ServerRendering.md) 9 | * [Writing Tests](WritingTests.md) 10 | * [Computing Derived Data](ComputingDerivedData.md) 11 | * [Implementing Undo History](ImplementingUndoHistory.md) 12 | * [Isolating Subapps](IsolatingSubapps.md) 13 | 14 | -------------------------------------------------------------------------------- /examples/async/src/components/Picker.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Picker = ({ value, onChange, options }) => ( 4 | 5 |

    {value}

    6 | 14 |
    15 | ) 16 | 17 | Picker.propTypes = { 18 | options: PropTypes.arrayOf( 19 | PropTypes.string.isRequired 20 | ).isRequired, 21 | value: PropTypes.string.isRequired, 22 | onChange: PropTypes.func.isRequired 23 | } 24 | 25 | export default Picker 26 | -------------------------------------------------------------------------------- /examples/todos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todos", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "enzyme": "^2.4.1", 7 | "react-addons-test-utils": "^15.3.0", 8 | "react-scripts": "^0.6.0" 9 | }, 10 | "dependencies": { 11 | "react": "^15.3.0", 12 | "react-dom": "^15.3.0", 13 | "react-redux": "^4.4.5", 14 | "redux": "^3.5.2" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "eject": "react-scripts eject", 20 | "test": "react-scripts test" 21 | }, 22 | "eslintConfig": { 23 | "extends": "./node_modules/react-scripts/config/eslint.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/todos/src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Todo from './Todo' 3 | 4 | const TodoList = ({ todos, onTodoClick }) => ( 5 |
      6 | {todos.map(todo => 7 | onTodoClick(todo.id)} 11 | /> 12 | )} 13 |
    14 | ) 15 | 16 | TodoList.propTypes = { 17 | todos: PropTypes.arrayOf(PropTypes.shape({ 18 | id: PropTypes.number.isRequired, 19 | completed: PropTypes.bool.isRequired, 20 | text: PropTypes.string.isRequired 21 | }).isRequired).isRequired, 22 | onTodoClick: PropTypes.func.isRequired 23 | } 24 | 25 | export default TodoList 26 | -------------------------------------------------------------------------------- /examples/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "enzyme": "^2.4.1", 7 | "react-addons-test-utils": "^15.3.0", 8 | "react-scripts": "^0.6.0" 9 | }, 10 | "dependencies": { 11 | "react": "^15.3.0", 12 | "react-dom": "^15.3.0", 13 | "react-redux": "^4.4.5", 14 | "redux": "^3.5.2" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "eject": "react-scripts eject", 20 | "test": "react-scripts test" 21 | }, 22 | "eslintConfig": { 23 | "extends": "./node_modules/react-scripts/config/eslint.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/todos-flow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todos", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "enzyme": "^2.4.1", 7 | "react-addons-test-utils": "^15.3.0", 8 | "react-scripts": "^0.6.0" 9 | }, 10 | "dependencies": { 11 | "react": "^15.3.0", 12 | "react-dom": "^15.3.0", 13 | "react-redux": "^4.4.5", 14 | "redux": "^3.5.2" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "eject": "react-scripts eject", 20 | "test": "react-scripts test" 21 | }, 22 | "eslintConfig": { 23 | "extends": "./node_modules/react-scripts/config/eslint.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/universal/common/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import rootReducer from '../reducers' 4 | 5 | const configureStore = (preloadedState) => { 6 | const store = createStore( 7 | rootReducer, 8 | preloadedState, 9 | applyMiddleware(thunk) 10 | ) 11 | 12 | if (module.hot) { 13 | // Enable Webpack hot module replacement for reducers 14 | module.hot.accept('../reducers', () => { 15 | const nextRootReducer = require('../reducers').default 16 | store.replaceReducer(nextRootReducer) 17 | }) 18 | } 19 | 20 | return store 21 | } 22 | 23 | export default configureStore 24 | -------------------------------------------------------------------------------- /examples/todomvc/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import TodoTextInput from './TodoTextInput' 3 | 4 | export default class Header extends Component { 5 | static propTypes = { 6 | addTodo: PropTypes.func.isRequired 7 | } 8 | 9 | handleSave = text => { 10 | if (text.length !== 0) { 11 | this.props.addTodo(text) 12 | } 13 | } 14 | 15 | render() { 16 | return ( 17 |
    18 |

    todos

    19 | 22 |
    23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Todo from './Todo' 3 | 4 | const TodoList = ({ todos, onTodoClick }) => ( 5 |
      6 | {todos.map(todo => 7 | onTodoClick(todo.id)} 11 | /> 12 | )} 13 |
    14 | ) 15 | 16 | TodoList.propTypes = { 17 | todos: PropTypes.arrayOf(PropTypes.shape({ 18 | id: PropTypes.number.isRequired, 19 | completed: PropTypes.bool.isRequired, 20 | text: PropTypes.string.isRequired 21 | }).isRequired).isRequired, 22 | onTodoClick: PropTypes.func.isRequired 23 | } 24 | 25 | export default TodoList 26 | -------------------------------------------------------------------------------- /examples/async/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { createStore, applyMiddleware } from 'redux' 4 | import { Provider } from 'react-redux' 5 | import thunk from 'redux-thunk' 6 | import createLogger from 'redux-logger' 7 | import reducer from './reducers' 8 | import App from './containers/App' 9 | 10 | const middleware = [ thunk ] 11 | if (process.env.NODE_ENV !== 'production') { 12 | middleware.push(createLogger()) 13 | } 14 | 15 | const store = createStore( 16 | reducer, 17 | applyMiddleware(...middleware) 18 | ) 19 | 20 | render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ) 26 | -------------------------------------------------------------------------------- /examples/tree-view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-view", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "deep-freeze": "0.0.1", 7 | "enzyme": "^2.4.1", 8 | "react-addons-test-utils": "^15.3.0", 9 | "react-scripts": "^0.6.0" 10 | }, 11 | "dependencies": { 12 | "react": "^15.3.0", 13 | "react-dom": "^15.3.0", 14 | "react-redux": "^4.4.5", 15 | "redux": "^3.5.2" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "eject": "react-scripts eject", 21 | "test": "react-scripts test" 22 | }, 23 | "eslintConfig": { 24 | "extends": "./node_modules/react-scripts/config/eslint.js" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/warning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a warning in the console if it exists. 3 | * 4 | * @param {String} message The warning message. 5 | * @returns {void} 6 | */ 7 | export default function warning(message) { 8 | /* eslint-disable no-console */ 9 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 10 | console.error(message) 11 | } 12 | /* eslint-enable no-console */ 13 | try { 14 | // This error was thrown as a convenience so that if you enable 15 | // "break on all exceptions" in your console, 16 | // it would pause the execution at this line. 17 | throw new Error(message) 18 | /* eslint-disable no-empty */ 19 | } catch (e) { } 20 | /* eslint-enable no-empty */ 21 | } 22 | -------------------------------------------------------------------------------- /examples/real-world/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React App 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/real-world/src/components/User.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | 4 | const User = ({ user }) => { 5 | const { login, avatarUrl, name } = user 6 | 7 | return ( 8 |
    9 | 10 | {login} 11 |

    12 | {login} {name && ({name})} 13 |

    14 | 15 |
    16 | ) 17 | } 18 | 19 | User.propTypes = { 20 | user: PropTypes.shape({ 21 | login: PropTypes.string.isRequired, 22 | avatarUrl: PropTypes.string.isRequired, 23 | name: PropTypes.string 24 | }).isRequired 25 | } 26 | 27 | export default User 28 | -------------------------------------------------------------------------------- /examples/async/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Async Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/todos/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Todos Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/todos/src/containers/AddTodo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { addTodo } from '../actions' 4 | 5 | let AddTodo = ({ dispatch }) => { 6 | let input 7 | 8 | return ( 9 |
    10 |
    { 11 | e.preventDefault() 12 | if (!input.value.trim()) { 13 | return 14 | } 15 | dispatch(addTodo(input.value)) 16 | input.value = '' 17 | }}> 18 | { 19 | input = node 20 | }} /> 21 | 24 |
    25 |
    26 | ) 27 | } 28 | AddTodo = connect()(AddTodo) 29 | 30 | export default AddTodo 31 | -------------------------------------------------------------------------------- /examples/counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Counter Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/todos-flow/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Todos Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": ">=3.2.1", 3 | "title": "Redux", 4 | "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs", "algolia"], 5 | "pluginsConfig": { 6 | "edit-link": { 7 | "base": "https://github.com/reactjs/redux/tree/master", 8 | "label": "Edit This Page" 9 | }, 10 | "github": { 11 | "url": "https://github.com/reactjs/redux/" 12 | }, 13 | "algolia": { 14 | "index": "redux-docs", 15 | "applicationID": "BC2QB3G2G4", 16 | "publicKey": "75eb88bb56d8cf8d17b6366a2c8e5fcd", 17 | "freeAccount": "false" 18 | }, 19 | "theme-default": { 20 | "showLevel": true, 21 | "styles": { 22 | "website": "build/gitbook.css" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/containers/AddTodo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { addTodo } from '../actions' 4 | 5 | let AddTodo = ({ dispatch }) => { 6 | let input 7 | 8 | return ( 9 |
    10 |
    { 11 | e.preventDefault() 12 | if (!input.value.trim()) { 13 | return 14 | } 15 | dispatch(addTodo(input.value)) 16 | input.value = '' 17 | }}> 18 | { 19 | input = node 20 | }} /> 21 | 24 |
    25 |
    26 | ) 27 | } 28 | AddTodo = connect()(AddTodo) 29 | 30 | export default AddTodo 31 | -------------------------------------------------------------------------------- /examples/tree-view/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Tree View Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/shopping-cart/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Shopping Cart Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/todos/src/actions/index.spec.js: -------------------------------------------------------------------------------- 1 | import * as actions from './index' 2 | 3 | describe('todo actions', () => { 4 | it('addTodo should create ADD_TODO action', () => { 5 | expect(actions.addTodo('Use Redux')).toEqual({ 6 | type: 'ADD_TODO', 7 | id: 0, 8 | text: 'Use Redux' 9 | }) 10 | }) 11 | 12 | it('setVisibilityFilter should create SET_VISIBILITY_FILTER action', () => { 13 | expect(actions.setVisibilityFilter('active')).toEqual({ 14 | type: 'SET_VISIBILITY_FILTER', 15 | filter: 'active' 16 | }) 17 | }) 18 | 19 | it('toggleTodo should create TOGGLE_TODO action', () => { 20 | expect(actions.toggleTodo(1)).toEqual({ 21 | type: 'TOGGLE_TODO', 22 | id: 1 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /examples/todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "enzyme": "^2.4.1", 7 | "react-addons-test-utils": "^15.3.0", 8 | "react-scripts": "^0.6.0" 9 | }, 10 | "dependencies": { 11 | "classnames": "^2.2.5", 12 | "react": "^15.3.0", 13 | "react-dom": "^15.3.0", 14 | "react-redux": "^4.4.5", 15 | "redux": "^3.5.2", 16 | "todomvc-app-css": "^2.0.6" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "eject": "react-scripts eject", 22 | "test": "react-scripts test" 23 | }, 24 | "eslintConfig": { 25 | "extends": "./node_modules/react-scripts/config/eslint.js" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/todomvc/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux TodoMVC Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/todos-flow/src/actions/index.spec.js: -------------------------------------------------------------------------------- 1 | import * as actions from './index' 2 | 3 | describe('todo actions', () => { 4 | it('addTodo should create ADD_TODO action', () => { 5 | expect(actions.addTodo('Use Redux')).toEqual({ 6 | type: 'ADD_TODO', 7 | id: 0, 8 | text: 'Use Redux' 9 | }) 10 | }) 11 | 12 | it('setVisibilityFilter should create SET_VISIBILITY_FILTER action', () => { 13 | expect(actions.setVisibilityFilter('active')).toEqual({ 14 | type: 'SET_VISIBILITY_FILTER', 15 | filter: 'active' 16 | }) 17 | }) 18 | 19 | it('toggleTodo should create TOGGLE_TODO action', () => { 20 | expect(actions.toggleTodo(1)).toEqual({ 21 | type: 'TOGGLE_TODO', 22 | id: 1 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /examples/todos-with-undo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Redux Todos with Undo Example 7 | 8 | 9 |
    10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/shopping-cart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopping-cart", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "enzyme": "^2.4.1", 7 | "react-addons-test-utils": "^15.3.0", 8 | "react-scripts": "^0.6.0" 9 | }, 10 | "dependencies": { 11 | "react": "^15.3.0", 12 | "react-dom": "^15.3.0", 13 | "react-redux": "^4.4.5", 14 | "redux": "^3.5.2", 15 | "redux-logger": "^2.6.1", 16 | "redux-thunk": "^2.1.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "eject": "react-scripts eject", 22 | "test": "react-scripts test" 23 | }, 24 | "eslintConfig": { 25 | "extends": "./node_modules/react-scripts/config/eslint.js" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a *feature* or report a *bug*?** 2 | 3 | (If this is a *usage question*, please **do not post it here**—post it on [Stack Overflow](http://stackoverflow.com/questions/tagged/redux) instead. If this is not a “feature” or a “bug”, or the phrase “How do I...?” applies, then it's probably a usage question.) 4 | 5 | 6 | 7 | **What is the current behavior?** 8 | 9 | 10 | 11 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar.** 12 | 13 | 14 | 15 | **What is the expected behavior?** 16 | 17 | 18 | 19 | **Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?** 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { createStore, applyMiddleware } from 'redux' 4 | import { Provider } from 'react-redux' 5 | import createLogger from 'redux-logger' 6 | import thunk from 'redux-thunk' 7 | import reducer from './reducers' 8 | import { getAllProducts } from './actions' 9 | import App from './containers/App' 10 | 11 | const middleware = [ thunk ]; 12 | if (process.env.NODE_ENV !== 'production') { 13 | middleware.push(createLogger()); 14 | } 15 | 16 | const store = createStore( 17 | reducer, 18 | applyMiddleware(...middleware) 19 | ) 20 | 21 | store.dispatch(getAllProducts()) 22 | 23 | render( 24 | 25 | 26 | , 27 | document.getElementById('root') 28 | ) 29 | -------------------------------------------------------------------------------- /examples/universal/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | devtool: 'inline-source-map', 6 | entry: [ 7 | 'webpack-hot-middleware/client', 8 | './client/index.js' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/static/' 14 | }, 15 | plugins: [ 16 | new webpack.optimize.OccurrenceOrderPlugin(), 17 | new webpack.HotModuleReplacementPlugin() 18 | ], 19 | module: { 20 | loaders: [ 21 | { 22 | test: /\.js$/, 23 | loader: 'babel', 24 | exclude: /node_modules/, 25 | include: __dirname, 26 | query: { 27 | presets: [ 'react-hmre' ] 28 | } 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/tree-view/src/actions/index.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT = 'INCREMENT' 2 | export const CREATE_NODE = 'CREATE_NODE' 3 | export const DELETE_NODE = 'DELETE_NODE' 4 | export const ADD_CHILD = 'ADD_CHILD' 5 | export const REMOVE_CHILD = 'REMOVE_CHILD' 6 | 7 | export const increment = (nodeId) => ({ 8 | type: INCREMENT, 9 | nodeId 10 | }) 11 | 12 | let nextId = 0 13 | export const createNode = () => ({ 14 | type: CREATE_NODE, 15 | nodeId: `new_${nextId++}` 16 | }) 17 | 18 | export const deleteNode = (nodeId) => ({ 19 | type: DELETE_NODE, 20 | nodeId 21 | }) 22 | 23 | export const addChild = (nodeId, childId) => ({ 24 | type: ADD_CHILD, 25 | nodeId, 26 | childId 27 | }) 28 | 29 | export const removeChild = (nodeId, childId) => ({ 30 | type: REMOVE_CHILD, 31 | nodeId, 32 | childId 33 | }) 34 | -------------------------------------------------------------------------------- /examples/universal/common/actions/index.js: -------------------------------------------------------------------------------- 1 | export const SET_COUNTER = 'SET_COUNTER' 2 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER' 3 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER' 4 | 5 | export const set = (value) => ({ 6 | type: SET_COUNTER, 7 | payload: value 8 | }) 9 | 10 | export const increment = () => ({ 11 | type: INCREMENT_COUNTER 12 | }) 13 | 14 | export const decrement = () => ({ 15 | type: DECREMENT_COUNTER 16 | }) 17 | 18 | export const incrementIfOdd = () => (dispatch, getState) => { 19 | const { counter } = getState() 20 | 21 | if (counter % 2 === 0) { 22 | return 23 | } 24 | 25 | dispatch(increment()) 26 | } 27 | 28 | export const incrementAsync = (delay = 1000) => dispatch => { 29 | setTimeout(() => { 30 | dispatch(increment()) 31 | }, delay) 32 | } 33 | -------------------------------------------------------------------------------- /examples/universal/common/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | const Counter = ({increment, incrementIfOdd, incrementAsync, decrement, counter}) => ( 4 |

    5 | Clicked: {counter} times 6 | {' '} 7 | 8 | {' '} 9 | 10 | {' '} 11 | 12 | {' '} 13 | 14 |

    15 | ) 16 | 17 | Counter.propTypes = { 18 | increment: PropTypes.func.isRequired, 19 | incrementIfOdd: PropTypes.func.isRequired, 20 | incrementAsync: PropTypes.func.isRequired, 21 | decrement: PropTypes.func.isRequired, 22 | counter: PropTypes.number.isRequired 23 | } 24 | 25 | export default Counter 26 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/ProductItem.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Product from './Product' 3 | 4 | const ProductItem = ({ product, onAddToCartClicked }) => ( 5 |
    6 | 9 | 14 |
    15 | ) 16 | 17 | ProductItem.propTypes = { 18 | product: PropTypes.shape({ 19 | title: PropTypes.string.isRequired, 20 | price: PropTypes.number.isRequired, 21 | inventory: PropTypes.number.isRequired 22 | }).isRequired, 23 | onAddToCartClicked: PropTypes.func.isRequired 24 | } 25 | 26 | export default ProductItem 27 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/containers/UndoRedo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ActionCreators as UndoActionCreators } from 'redux-undo' 3 | import { connect } from 'react-redux' 4 | 5 | let UndoRedo = ({ canUndo, canRedo, onUndo, onRedo }) => ( 6 |

    7 | 10 | 13 |

    14 | ) 15 | 16 | const mapStateToProps = (state) => ({ 17 | canUndo: state.todos.past.length > 0, 18 | canRedo: state.todos.future.length > 0 19 | }) 20 | 21 | const mapDispatchToProps = ({ 22 | onUndo: UndoActionCreators.undo, 23 | onRedo: UndoActionCreators.redo 24 | }) 25 | 26 | UndoRedo = connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(UndoRedo) 30 | 31 | export default UndoRedo 32 | -------------------------------------------------------------------------------- /src/compose.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Composes single-argument functions from right to left. The rightmost 3 | * function can take multiple arguments as it provides the signature for 4 | * the resulting composite function. 5 | * 6 | * @param {...Function} funcs The functions to compose. 7 | * @returns {Function} A function obtained by composing the argument functions 8 | * from right to left. For example, compose(f, g, h) is identical to doing 9 | * (...args) => f(g(h(...args))). 10 | */ 11 | 12 | export default function compose(...funcs) { 13 | if (funcs.length === 0) { 14 | return arg => arg 15 | } 16 | 17 | if (funcs.length === 1) { 18 | return funcs[0] 19 | } 20 | 21 | const last = funcs[funcs.length - 1] 22 | const rest = funcs.slice(0, -1) 23 | return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) 24 | } 25 | -------------------------------------------------------------------------------- /examples/todos-flow/src/types/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Store as ReduxStore, Dispatch as ReduxDispatch } from 'redux' 3 | 4 | export type Id = number; 5 | 6 | export type Text = string; 7 | 8 | export type Todo = { 9 | id: Id, 10 | text: Text, 11 | completed: boolean 12 | }; 13 | 14 | export type VisibilityFilter = 15 | 'SHOW_ALL' 16 | | 'SHOW_ACTIVE' 17 | | 'SHOW_COMPLETED' 18 | ; 19 | 20 | export type Todos = Array; 21 | 22 | export type State = { 23 | todos: Todos, 24 | visibilityFilter: VisibilityFilter 25 | }; 26 | 27 | export type Action = 28 | { type: 'ADD_TODO', id: Id, text: Text } 29 | | { type: 'TOGGLE_TODO', id: Id } 30 | | { type: 'SET_VISIBILITY_FILTER', filter: VisibilityFilter } 31 | ; 32 | 33 | export type Store = ReduxStore; 34 | 35 | export type Dispatch = ReduxDispatch; 36 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/Product.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import Product from './Product' 4 | 5 | const setup = props => { 6 | const component = shallow( 7 | 8 | ) 9 | 10 | return { 11 | component: component 12 | } 13 | } 14 | 15 | describe('Product component', () => { 16 | it('should render title and price', () => { 17 | const { component } = setup({ title: 'Test Product', price: 9.99 }) 18 | expect(component.text()).toBe('Test Product - $9.99') 19 | }) 20 | 21 | describe('when given quantity', () => { 22 | it('should render title, price, and quantity', () => { 23 | const { component } = setup({ title: 'Test Product', price: 9.99, quantity: 6 }) 24 | expect(component.text()).toBe('Test Product - $9.99 x 6') 25 | }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack') 4 | 5 | var env = process.env.NODE_ENV 6 | var config = { 7 | module: { 8 | loaders: [ 9 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ } 10 | ] 11 | }, 12 | output: { 13 | library: 'Redux', 14 | libraryTarget: 'umd' 15 | }, 16 | plugins: [ 17 | new webpack.optimize.OccurrenceOrderPlugin(), 18 | new webpack.DefinePlugin({ 19 | 'process.env.NODE_ENV': JSON.stringify(env) 20 | }) 21 | ] 22 | }; 23 | 24 | if (env === 'production') { 25 | config.plugins.push( 26 | new webpack.optimize.UglifyJsPlugin({ 27 | compressor: { 28 | pure_getters: true, 29 | unsafe: true, 30 | unsafe_comps: true, 31 | warnings: false 32 | } 33 | }) 34 | ) 35 | } 36 | 37 | module.exports = config 38 | -------------------------------------------------------------------------------- /PATRONS.md: -------------------------------------------------------------------------------- 1 | # Patrons 2 | 3 | The work on Redux was [funded by the community](https://www.patreon.com/reactdx). 4 | Meet some of the outstanding companies and individuals that made it possible: 5 | 6 | * [Webflow](https://github.com/webflow) 7 | * [Ximedes](https://www.ximedes.com/) 8 | * [HauteLook](http://hautelook.github.io/) 9 | * [Ken Wheeler](http://kenwheeler.github.io/) 10 | * [Chung Yen Li](https://www.facebook.com/prototocal.lee) 11 | * [Sunil Pai](https://twitter.com/threepointone) 12 | * [Charlie Cheever](https://twitter.com/ccheever) 13 | * [Eugene G](https://twitter.com/e1g) 14 | * [Matt Apperson](https://twitter.com/mattapperson) 15 | * [Jed Watson](https://twitter.com/jedwatson) 16 | * [Sasha Aickin](https://twitter.com/xander76) 17 | * [Stefan Tennigkeit](https://twitter.com/whobubble) 18 | * [Sam Vincent](https://twitter.com/samvincent) 19 | * Olegzandr Denman 20 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import cart, * as fromCart from './cart' 3 | import products, * as fromProducts from './products' 4 | 5 | export default combineReducers({ 6 | cart, 7 | products 8 | }) 9 | 10 | const getAddedIds = state => fromCart.getAddedIds(state.cart) 11 | const getQuantity = (state, id) => fromCart.getQuantity(state.cart, id) 12 | const getProduct = (state, id) => fromProducts.getProduct(state.products, id) 13 | 14 | export const getTotal = state => 15 | getAddedIds(state) 16 | .reduce((total, id) => 17 | total + getProduct(state, id).price * getQuantity(state, id), 18 | 0 19 | ) 20 | .toFixed(2) 21 | 22 | export const getCartProducts = state => 23 | getAddedIds(state).map(id => ({ 24 | ...getProduct(state, id), 25 | quantity: getQuantity(state, id) 26 | })) 27 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/ProductsList.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import ProductsList from './ProductsList' 4 | 5 | const setup = props => { 6 | const component = shallow( 7 | {props.children} 8 | ) 9 | 10 | return { 11 | component: component, 12 | children: component.children().at(1), 13 | h3: component.find('h3') 14 | } 15 | } 16 | 17 | describe('ProductsList component', () => { 18 | it('should render title', () => { 19 | const { h3 } = setup({ title: 'Test Products' }) 20 | expect(h3.text()).toMatch(/^Test Products$/) 21 | }) 22 | 23 | it('should render children', () => { 24 | const { children } = setup({ title: 'Test Products', children: 'Test Children' }) 25 | expect(children.text()).toMatch(/^Test Children$/) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/todos-flow/src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Todos, Todo, Id, Text, Action } from '../types' 3 | 4 | function createTodo(id: Id, text: Text): Todo { 5 | return { 6 | id, 7 | text, 8 | completed: false 9 | } 10 | } 11 | 12 | function toggleTodo(todos: Todos, id: Id): Todos { 13 | return todos.map(t => { 14 | if (t.id !== id) { 15 | return t 16 | } 17 | return Object.assign({}, t, { 18 | completed: !t.completed 19 | }) 20 | }) 21 | } 22 | 23 | const todos = (state: Todos = [], action: Action): Todos => { 24 | switch (action.type) { 25 | case 'ADD_TODO': 26 | return [ 27 | ...state, 28 | createTodo(action.id, action.text) 29 | ] 30 | case 'TOGGLE_TODO': 31 | return toggleTodo(state, action.id) 32 | default: 33 | return state 34 | } 35 | } 36 | 37 | export default todos 38 | -------------------------------------------------------------------------------- /examples/todos/src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | const todo = (state, action) => { 2 | switch (action.type) { 3 | case 'ADD_TODO': 4 | return { 5 | id: action.id, 6 | text: action.text, 7 | completed: false 8 | } 9 | case 'TOGGLE_TODO': 10 | if (state.id !== action.id) { 11 | return state 12 | } 13 | 14 | return { 15 | ...state, 16 | completed: !state.completed 17 | } 18 | default: 19 | return state 20 | } 21 | } 22 | 23 | const todos = (state = [], action) => { 24 | switch (action.type) { 25 | case 'ADD_TODO': 26 | return [ 27 | ...state, 28 | todo(undefined, action) 29 | ] 30 | case 'TOGGLE_TODO': 31 | return state.map(t => 32 | todo(t, action) 33 | ) 34 | default: 35 | return state 36 | } 37 | } 38 | 39 | export default todos 40 | -------------------------------------------------------------------------------- /examples/todos/src/containers/VisibleTodoList.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { toggleTodo } from '../actions' 3 | import TodoList from '../components/TodoList' 4 | 5 | const getVisibleTodos = (todos, filter) => { 6 | switch (filter) { 7 | case 'SHOW_ALL': 8 | return todos 9 | case 'SHOW_COMPLETED': 10 | return todos.filter(t => t.completed) 11 | case 'SHOW_ACTIVE': 12 | return todos.filter(t => !t.completed) 13 | default: 14 | throw new Error('Unknown filter: ' + filter) 15 | } 16 | } 17 | 18 | const mapStateToProps = (state) => ({ 19 | todos: getVisibleTodos(state.todos, state.visibilityFilter) 20 | }) 21 | 22 | const mapDispatchToProps = ({ 23 | onTodoClick: toggleTodo 24 | }) 25 | 26 | const VisibleTodoList = connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(TodoList) 30 | 31 | export default VisibleTodoList 32 | -------------------------------------------------------------------------------- /examples/todomvc/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { connect } from 'react-redux' 4 | import Header from '../components/Header' 5 | import MainSection from '../components/MainSection' 6 | import * as TodoActions from '../actions' 7 | 8 | const App = ({todos, actions}) => ( 9 |
    10 |
    11 | 12 |
    13 | ) 14 | 15 | App.propTypes = { 16 | todos: PropTypes.array.isRequired, 17 | actions: PropTypes.object.isRequired 18 | } 19 | 20 | const mapStateToProps = state => ({ 21 | todos: state.todos 22 | }) 23 | 24 | const mapDispatchToProps = dispatch => ({ 25 | actions: bindActionCreators(TodoActions, dispatch) 26 | }) 27 | 28 | export default connect( 29 | mapStateToProps, 30 | mapDispatchToProps 31 | )(App) 32 | -------------------------------------------------------------------------------- /examples/real-world/src/components/Repo.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | 4 | const Repo = ({ repo, owner }) => { 5 | const { login } = owner 6 | const { name, description } = repo 7 | 8 | return ( 9 |
    10 |

    11 | 12 | {name} 13 | 14 | {' by '} 15 | 16 | {login} 17 | 18 |

    19 | {description && 20 |

    {description}

    21 | } 22 |
    23 | ) 24 | } 25 | 26 | Repo.propTypes = { 27 | repo: PropTypes.shape({ 28 | name: PropTypes.string.isRequired, 29 | description: PropTypes.string 30 | }).isRequired, 31 | owner: PropTypes.shape({ 32 | login: PropTypes.string.isRequired 33 | }).isRequired 34 | } 35 | 36 | export default Repo 37 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/containers/VisibleTodoList.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { toggleTodo } from '../actions' 3 | import TodoList from '../components/TodoList' 4 | 5 | const getVisibleTodos = (todos, filter) => { 6 | switch (filter) { 7 | case 'SHOW_ALL': 8 | return todos 9 | case 'SHOW_COMPLETED': 10 | return todos.filter(t => t.completed) 11 | case 'SHOW_ACTIVE': 12 | return todos.filter(t => !t.completed) 13 | default: 14 | throw new Error('Unknown filter: ' + filter) 15 | } 16 | } 17 | 18 | const mapStateToProps = (state) => ({ 19 | todos: getVisibleTodos(state.todos.present, state.visibilityFilter) 20 | }) 21 | 22 | const mapDispatchToProps = ({ 23 | onTodoClick: toggleTodo 24 | }) 25 | 26 | const VisibleTodoList = connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(TodoList) 30 | 31 | export default VisibleTodoList 32 | -------------------------------------------------------------------------------- /examples/real-world/src/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import createLogger from 'redux-logger' 4 | import api from '../middleware/api' 5 | import rootReducer from '../reducers' 6 | import DevTools from '../containers/DevTools' 7 | 8 | const configureStore = preloadedState => { 9 | const store = createStore( 10 | rootReducer, 11 | preloadedState, 12 | compose( 13 | applyMiddleware(thunk, api, createLogger()), 14 | DevTools.instrument() 15 | ) 16 | ) 17 | 18 | if (module.hot) { 19 | // Enable Webpack hot module replacement for reducers 20 | module.hot.accept('../reducers', () => { 21 | const nextRootReducer = require('../reducers').default 22 | store.replaceReducer(nextRootReducer) 23 | }) 24 | } 25 | 26 | return store 27 | } 28 | 29 | export default configureStore 30 | -------------------------------------------------------------------------------- /test/helpers/actionCreators.js: -------------------------------------------------------------------------------- 1 | import { ADD_TODO, DISPATCH_IN_MIDDLE, THROW_ERROR, UNKNOWN_ACTION } from './actionTypes' 2 | 3 | export function addTodo(text) { 4 | return { type: ADD_TODO, text } 5 | } 6 | 7 | export function addTodoAsync(text) { 8 | return dispatch => new Promise(resolve => setImmediate(() => { 9 | dispatch(addTodo(text)) 10 | resolve() 11 | })) 12 | } 13 | 14 | export function addTodoIfEmpty(text) { 15 | return (dispatch, getState) => { 16 | if (!getState().length) { 17 | dispatch(addTodo(text)) 18 | } 19 | } 20 | } 21 | 22 | export function dispatchInMiddle(boundDispatchFn) { 23 | return { 24 | type: DISPATCH_IN_MIDDLE, 25 | boundDispatchFn 26 | } 27 | } 28 | 29 | export function throwError() { 30 | return { 31 | type: THROW_ERROR 32 | } 33 | } 34 | 35 | export function unknownAction() { 36 | return { 37 | type: UNKNOWN_ACTION 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/todos-flow/src/containers/FilterLink.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { connect } from 'react-redux' 3 | import { setVisibilityFilter } from '../actions' 4 | import Link from '../components/Link' 5 | import type { Props } from '../components/Link' 6 | import type { State, Dispatch, VisibilityFilter } from '../types' 7 | import type { Connector } from 'react-redux' 8 | 9 | type OwnProps = { 10 | filter: VisibilityFilter 11 | }; 12 | 13 | const mapStateToProps = (state: State, ownProps) => { 14 | return { 15 | active: ownProps.filter === state.visibilityFilter 16 | } 17 | } 18 | 19 | const mapDispatchToProps = (dispatch: Dispatch, ownProps) => { 20 | return { 21 | onClick: () => { 22 | dispatch(setVisibilityFilter(ownProps.filter)) 23 | } 24 | } 25 | } 26 | 27 | const connector: Connector = connect( 28 | mapStateToProps, 29 | mapDispatchToProps 30 | ) 31 | 32 | export default connector(Link) 33 | -------------------------------------------------------------------------------- /examples/real-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "real-world", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0", 7 | "redux-devtools": "^3.3.1", 8 | "redux-devtools-dock-monitor": "^1.1.1", 9 | "redux-devtools-log-monitor": "^1.0.11", 10 | "redux-logger": "^2.6.1" 11 | }, 12 | "dependencies": { 13 | "humps": "^1.1.0", 14 | "lodash": "^4.16.1", 15 | "normalizr": "^2.2.1", 16 | "react": "^15.3.0", 17 | "react-dom": "^15.3.0", 18 | "react-redux": "^4.4.5", 19 | "react-router": "^2.6.1", 20 | "react-router-redux": "^4.0.5", 21 | "redux": "^3.5.2", 22 | "redux-thunk": "^2.1.0" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": "./node_modules/react-scripts/config/eslint.js" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/todos-flow/src/containers/AddTodo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import { connect } from 'react-redux' 4 | import { addTodo } from '../actions' 5 | import type { Dispatch } from '../types' 6 | import type { Connector } from 'react-redux' 7 | 8 | type Props = { 9 | dispatch: Dispatch 10 | }; 11 | 12 | const AddTodo = ({ dispatch }) => { 13 | let input 14 | 15 | return ( 16 |
    17 |
    { 18 | e.preventDefault() 19 | if (!input.value.trim()) { 20 | return 21 | } 22 | dispatch(addTodo(input.value)) 23 | input.value = '' 24 | }}> 25 | { 26 | input = node 27 | }} /> 28 | 31 |
    32 |
    33 | ) 34 | } 35 | 36 | const connector: Connector<{}, Props> = connect() 37 | 38 | export default connector(AddTodo) 39 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/Cart.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import Product from './Product' 3 | 4 | const Cart = ({ products, total, onCheckoutClicked }) => { 5 | const hasProducts = products.length > 0 6 | const nodes = hasProducts ? ( 7 | products.map(product => 8 | 14 | ) 15 | ) : ( 16 | Please add some products to cart. 17 | ) 18 | 19 | return ( 20 |
    21 |

    Your Cart

    22 |
    {nodes}
    23 |

    Total: ${total}

    24 | 28 |
    29 | ) 30 | } 31 | 32 | Cart.propTypes = { 33 | products: PropTypes.array, 34 | total: PropTypes.string, 35 | onCheckoutClicked: PropTypes.func 36 | } 37 | 38 | export default Cart 39 | -------------------------------------------------------------------------------- /examples/todos-with-undo/src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | import undoable, { distinctState } from 'redux-undo' 2 | 3 | const todo = (state, action) => { 4 | switch (action.type) { 5 | case 'ADD_TODO': 6 | return { 7 | id: action.id, 8 | text: action.text, 9 | completed: false 10 | } 11 | case 'TOGGLE_TODO': 12 | if (state.id !== action.id) { 13 | return state 14 | } 15 | 16 | return { 17 | ...state, 18 | completed: !state.completed 19 | } 20 | default: 21 | return state 22 | } 23 | } 24 | 25 | const todos = (state = [], action) => { 26 | switch (action.type) { 27 | case 'ADD_TODO': 28 | return [ 29 | ...state, 30 | todo(undefined, action) 31 | ] 32 | case 'TOGGLE_TODO': 33 | return state.map(t => 34 | todo(t, action) 35 | ) 36 | default: 37 | return state 38 | } 39 | } 40 | 41 | const undoableTodos = undoable(todos, { 42 | filter: distinctState() 43 | }) 44 | 45 | export default undoableTodos 46 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/containers/CartContainer.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | import { checkout } from '../actions' 4 | import { getTotal, getCartProducts } from '../reducers' 5 | import Cart from '../components/Cart' 6 | 7 | const CartContainer = ({ products, total, checkout }) => ( 8 | checkout(products)} /> 12 | ) 13 | 14 | CartContainer.propTypes = { 15 | products: PropTypes.arrayOf(PropTypes.shape({ 16 | id: PropTypes.number.isRequired, 17 | title: PropTypes.string.isRequired, 18 | price: PropTypes.number.isRequired, 19 | quantity: PropTypes.number.isRequired 20 | })).isRequired, 21 | total: PropTypes.string, 22 | checkout: PropTypes.func.isRequired 23 | } 24 | 25 | const mapStateToProps = (state) => ({ 26 | products: getCartProducts(state), 27 | total: getTotal(state) 28 | }) 29 | 30 | export default connect( 31 | mapStateToProps, 32 | { checkout } 33 | )(CartContainer) 34 | -------------------------------------------------------------------------------- /examples/todos-flow/src/containers/VisibleTodoList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { connect } from 'react-redux' 3 | import { toggleTodo } from '../actions' 4 | import TodoList from '../components/TodoList' 5 | import type { State, Dispatch } from '../types' 6 | import type { Connector } from 'react-redux' 7 | import type { Props } from '../components/TodoList' 8 | 9 | const getVisibleTodos = (todos, filter) => { 10 | switch (filter) { 11 | case 'SHOW_COMPLETED': 12 | return todos.filter(t => t.completed) 13 | case 'SHOW_ACTIVE': 14 | return todos.filter(t => !t.completed) 15 | case 'SHOW_ALL': 16 | default : 17 | return todos 18 | } 19 | } 20 | 21 | const mapStateToProps = (state: State) => { 22 | return { 23 | todos: getVisibleTodos(state.todos, state.visibilityFilter) 24 | } 25 | } 26 | 27 | const mapDispatchToProps = (dispatch: Dispatch) => { 28 | return { 29 | onTodoClick: (id) => { 30 | dispatch(toggleTodo(id)) 31 | } 32 | } 33 | } 34 | 35 | const connector: Connector<{}, Props> = connect( 36 | mapStateToProps, 37 | mapDispatchToProps 38 | ) 39 | 40 | export default connector(TodoList) 41 | -------------------------------------------------------------------------------- /logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import shop from '../api/shop' 2 | import * as types from '../constants/ActionTypes' 3 | 4 | const receiveProducts = products => ({ 5 | type: types.RECEIVE_PRODUCTS, 6 | products: products 7 | }) 8 | 9 | export const getAllProducts = () => dispatch => { 10 | shop.getProducts(products => { 11 | dispatch(receiveProducts(products)) 12 | }) 13 | } 14 | 15 | const addToCartUnsafe = productId => ({ 16 | type: types.ADD_TO_CART, 17 | productId 18 | }) 19 | 20 | export const addToCart = productId => (dispatch, getState) => { 21 | if (getState().products.byId[productId].inventory > 0) { 22 | dispatch(addToCartUnsafe(productId)) 23 | } 24 | } 25 | 26 | export const checkout = products => (dispatch, getState) => { 27 | const { cart } = getState() 28 | 29 | dispatch({ 30 | type: types.CHECKOUT_REQUEST 31 | }) 32 | shop.buyProducts(products, () => { 33 | dispatch({ 34 | type: types.CHECKOUT_SUCCESS, 35 | cart 36 | }) 37 | // Replace the line above with line below to rollback on failure: 38 | // dispatch({ type: types.CHECKOUT_FAILURE, cart }) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Dan Abramov 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 | -------------------------------------------------------------------------------- /examples/buildAll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Runs an ordered set of commands within each of the build directories. 3 | */ 4 | 5 | var fs = require('fs') 6 | var path = require('path') 7 | var { spawnSync } = require('child_process') 8 | 9 | var exampleDirs = fs.readdirSync(__dirname).filter((file) => { 10 | return fs.statSync(path.join(__dirname, file)).isDirectory() 11 | }) 12 | 13 | // Ordering is important here. `npm install` must come first. 14 | var cmdArgs = [ 15 | { cmd: 'npm', args: [ 'install' ] }, 16 | { cmd: 'webpack', args: [ 'index.js' ] } 17 | ] 18 | 19 | for (const dir of exampleDirs) { 20 | for (const cmdArg of cmdArgs) { 21 | // declare opts in this scope to avoid https://github.com/joyent/node/issues/9158 22 | const opts = { 23 | cwd: path.join(__dirname, dir), 24 | stdio: 'inherit' 25 | } 26 | let result = {} 27 | if (process.platform === 'win32') { 28 | result = spawnSync(cmdArg.cmd + '.cmd', cmdArg.args, opts) 29 | } else { 30 | result = spawnSync(cmdArg.cmd, cmdArg.args, opts) 31 | } 32 | if (result.status !== 0) { 33 | throw new Error('Building examples exited with non-zero') 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/testAll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Runs an ordered set of commands within each of the build directories. 3 | */ 4 | 5 | var fs = require('fs') 6 | var path = require('path') 7 | var { spawnSync } = require('child_process') 8 | 9 | var exampleDirs = fs.readdirSync(__dirname).filter((file) => { 10 | return fs.statSync(path.join(__dirname, file)).isDirectory() 11 | }) 12 | 13 | // Ordering is important here. `npm install` must come first. 14 | var cmdArgs = [ 15 | { cmd: 'npm', args: [ 'install' ] }, 16 | { cmd: 'npm', args: [ 'test' ] } 17 | ] 18 | 19 | for (const dir of exampleDirs) { 20 | for (const cmdArg of cmdArgs) { 21 | // declare opts in this scope to avoid https://github.com/joyent/node/issues/9158 22 | const opts = { 23 | cwd: path.join(__dirname, dir), 24 | stdio: 'inherit' 25 | } 26 | 27 | let result = {} 28 | if (process.platform === 'win32') { 29 | result = spawnSync(cmdArg.cmd + '.cmd', cmdArg.args, opts) 30 | } else { 31 | result = spawnSync(cmdArg.cmd, cmdArg.args, opts) 32 | } 33 | if (result.status !== 0) { 34 | throw new Error('Building examples exited with non-zero') 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/counter/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class Counter extends Component { 4 | static propTypes = { 5 | value: PropTypes.number.isRequired, 6 | onIncrement: PropTypes.func.isRequired, 7 | onDecrement: PropTypes.func.isRequired 8 | } 9 | 10 | incrementIfOdd = () => { 11 | if (this.props.value % 2 !== 0) { 12 | this.props.onIncrement() 13 | } 14 | } 15 | 16 | incrementAsync = () => { 17 | setTimeout(this.props.onIncrement, 1000) 18 | } 19 | 20 | render() { 21 | const { value, onIncrement, onDecrement } = this.props 22 | return ( 23 |

    24 | Clicked: {value} times 25 | {' '} 26 | 29 | {' '} 30 | 33 | {' '} 34 | 37 | {' '} 38 | 41 |

    42 | ) 43 | } 44 | } 45 | 46 | export default Counter 47 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/containers/ProductsContainer.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | import { addToCart } from '../actions' 4 | import { getVisibleProducts } from '../reducers/products' 5 | import ProductItem from '../components/ProductItem' 6 | import ProductsList from '../components/ProductsList' 7 | 8 | const ProductsContainer = ({ products, addToCart }) => ( 9 | 10 | {products.map(product => 11 | addToCart(product.id)} /> 15 | )} 16 | 17 | ) 18 | 19 | ProductsContainer.propTypes = { 20 | products: PropTypes.arrayOf(PropTypes.shape({ 21 | id: PropTypes.number.isRequired, 22 | title: PropTypes.string.isRequired, 23 | price: PropTypes.number.isRequired, 24 | inventory: PropTypes.number.isRequired 25 | })).isRequired, 26 | addToCart: PropTypes.func.isRequired 27 | } 28 | 29 | const mapStateToProps = state => ({ 30 | products: getVisibleProducts(state.products) 31 | }) 32 | 33 | export default connect( 34 | mapStateToProps, 35 | { addToCart } 36 | )(ProductsContainer) 37 | -------------------------------------------------------------------------------- /test/typescript/actions.ts: -------------------------------------------------------------------------------- 1 | import {Action as ReduxAction} from "../../index.d.ts"; 2 | 3 | 4 | namespace FSA { 5 | interface Action

    extends ReduxAction { 6 | payload: P; 7 | } 8 | 9 | const action: Action = { 10 | type: 'ACTION_TYPE', 11 | payload: 'test', 12 | } 13 | 14 | const payload: string = action.payload; 15 | } 16 | 17 | 18 | namespace FreeShapeAction { 19 | interface Action extends ReduxAction { 20 | [key: string]: any; 21 | } 22 | 23 | const action: Action = { 24 | type: 'ACTION_TYPE', 25 | text: 'test', 26 | } 27 | 28 | const text: string = action['text']; 29 | } 30 | 31 | 32 | namespace StringLiteralTypeAction { 33 | type ActionType = 'A' | 'B' | 'C'; 34 | 35 | interface Action extends ReduxAction { 36 | type: ActionType; 37 | } 38 | 39 | const action: Action = { 40 | type: 'A' 41 | } 42 | 43 | const type: ActionType = action.type; 44 | } 45 | 46 | 47 | namespace EnumTypeAction { 48 | enum ActionType { 49 | A, B, C 50 | } 51 | 52 | interface Action extends ReduxAction { 53 | type: ActionType; 54 | } 55 | 56 | const action: Action = { 57 | type: ActionType.A 58 | } 59 | 60 | const type: ActionType = action.type; 61 | } 62 | -------------------------------------------------------------------------------- /examples/universal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-universal-example", 3 | "version": "0.0.0", 4 | "description": "An example of a universally-rendered Redux application", 5 | "scripts": { 6 | "start": "node server/index.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/reactjs/redux.git" 11 | }, 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/reactjs/redux/issues" 15 | }, 16 | "homepage": "http://redux.js.org", 17 | "dependencies": { 18 | "babel-polyfill": "^6.3.14", 19 | "babel-register": "^6.4.3", 20 | "express": "^4.13.3", 21 | "qs": "^4.0.0", 22 | "react": "^0.14.7", 23 | "react-dom": "^0.14.7", 24 | "react-redux": "^4.2.1", 25 | "redux": "^3.2.1", 26 | "redux-thunk": "^1.0.3", 27 | "serve-static": "^1.10.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.3.15", 31 | "babel-loader": "^6.2.0", 32 | "babel-preset-es2015": "^6.3.13", 33 | "babel-preset-react": "^6.3.13", 34 | "babel-preset-react-hmre": "^1.1.1", 35 | "babel-runtime": "^6.3.13", 36 | "webpack": "^1.11.0", 37 | "webpack-dev-middleware": "^1.4.0", 38 | "webpack-hot-middleware": "^2.9.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import createStore from './createStore' 2 | import combineReducers from './combineReducers' 3 | import bindActionCreators from './bindActionCreators' 4 | import applyMiddleware from './applyMiddleware' 5 | import compose from './compose' 6 | import warning from './utils/warning' 7 | 8 | /* 9 | * This is a dummy function to check if the function name has been altered by minification. 10 | * If the function has been minified and NODE_ENV !== 'production', warn the user. 11 | */ 12 | function isCrushed() {} 13 | 14 | if ( 15 | process.env.NODE_ENV !== 'production' && 16 | typeof isCrushed.name === 'string' && 17 | isCrushed.name !== 'isCrushed' 18 | ) { 19 | warning( 20 | 'You are currently using minified code outside of NODE_ENV === \'production\'. ' + 21 | 'This means that you are running a slower development build of Redux. ' + 22 | 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 23 | 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 24 | 'to ensure you have the correct code for your production build.' 25 | ) 26 | } 27 | 28 | export { 29 | createStore, 30 | combineReducers, 31 | bindActionCreators, 32 | applyMiddleware, 33 | compose 34 | } 35 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["transform-es2015-template-literals", { "loose": true }], 4 | "transform-es2015-literals", 5 | "transform-es2015-function-name", 6 | "transform-es2015-arrow-functions", 7 | "transform-es2015-block-scoped-functions", 8 | ["transform-es2015-classes", { "loose": true }], 9 | "transform-es2015-object-super", 10 | "transform-es2015-shorthand-properties", 11 | ["transform-es2015-computed-properties", { "loose": true }], 12 | ["transform-es2015-for-of", { "loose": true }], 13 | "transform-es2015-sticky-regex", 14 | "transform-es2015-unicode-regex", 15 | "check-es2015-constants", 16 | ["transform-es2015-spread", { "loose": true }], 17 | "transform-es2015-parameters", 18 | ["transform-es2015-destructuring", { "loose": true }], 19 | "transform-es2015-block-scoping", 20 | "transform-object-rest-spread", 21 | "transform-es3-member-expression-literals", 22 | "transform-es3-property-literals" 23 | ], 24 | "env": { 25 | "commonjs": { 26 | "plugins": [ 27 | ["transform-es2015-modules-commonjs", { "loose": true }] 28 | ] 29 | }, 30 | "es": { 31 | "plugins": [ 32 | "./build/use-lodash-es" 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/helpers/reducers.js: -------------------------------------------------------------------------------- 1 | import { ADD_TODO, DISPATCH_IN_MIDDLE, THROW_ERROR } from './actionTypes' 2 | 3 | 4 | function id(state = []) { 5 | return state.reduce((result, item) => ( 6 | item.id > result ? item.id : result 7 | ), 0) + 1 8 | } 9 | 10 | export function todos(state = [], action) { 11 | switch (action.type) { 12 | case ADD_TODO: 13 | return [ 14 | ...state, 15 | { 16 | id: id(state), 17 | text: action.text 18 | } 19 | ] 20 | default: 21 | return state 22 | } 23 | } 24 | 25 | export function todosReverse(state = [], action) { 26 | switch (action.type) { 27 | case ADD_TODO: 28 | return [ 29 | { 30 | id: id(state), 31 | text: action.text 32 | }, ...state 33 | ] 34 | default: 35 | return state 36 | } 37 | } 38 | 39 | export function dispatchInTheMiddleOfReducer(state = [], action) { 40 | switch (action.type) { 41 | case DISPATCH_IN_MIDDLE: 42 | action.boundDispatchFn() 43 | return state 44 | default: 45 | return state 46 | } 47 | } 48 | 49 | export function errorThrowingReducer(state = [], action) { 50 | switch (action.type) { 51 | case THROW_ERROR: 52 | throw new Error() 53 | default: 54 | return state 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/utils/warning.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import warning from '../../src/utils/warning' 3 | 4 | describe('Utils', () => { 5 | describe('warning', () => { 6 | it('calls console.error when available', () => { 7 | const preSpy = console.error 8 | const spy = jest.fn() 9 | console.error = spy 10 | try { 11 | warning('Test') 12 | expect(spy.mock.calls[0][0]).toBe('Test') 13 | } finally { 14 | spy.mockClear() 15 | console.error = preSpy 16 | } 17 | }) 18 | 19 | it('does not throw when console.error is not available', () => { 20 | const realConsole = global.console 21 | Object.defineProperty(global, 'console', { value: {} }) 22 | try { 23 | expect(() => warning('Test')).not.toThrow() 24 | } finally { 25 | Object.defineProperty(global, 'console', { value: realConsole }) 26 | } 27 | }) 28 | 29 | it('does not throw when console is not available', () => { 30 | const realConsole = global.console 31 | Object.defineProperty(global, 'console', { value: undefined }) 32 | try { 33 | expect(() => warning('Test')).not.toThrow() 34 | } finally { 35 | Object.defineProperty(global, 'console', { value: realConsole }) 36 | } 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/typescript/reducers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Reducer, Action, combineReducers, 3 | ReducersMapObject 4 | } from "../../index.d.ts"; 5 | 6 | 7 | type TodosState = string[]; 8 | 9 | interface AddTodoAction extends Action { 10 | text: string; 11 | } 12 | 13 | 14 | const todosReducer: Reducer = (state: TodosState, 15 | action: Action): TodosState => { 16 | switch (action.type) { 17 | case 'ADD_TODO': 18 | return [...state, (action).text] 19 | default: 20 | return state 21 | } 22 | } 23 | 24 | const todosState: TodosState = todosReducer([], { 25 | type: 'ADD_TODO', 26 | text: 'test', 27 | }); 28 | 29 | 30 | type CounterState = number; 31 | 32 | 33 | const counterReducer: Reducer = ( 34 | state: CounterState, action: Action 35 | ): CounterState => { 36 | switch (action.type) { 37 | case 'INCREMENT': 38 | return state + 1 39 | default: 40 | return state 41 | } 42 | } 43 | 44 | 45 | type RootState = { 46 | todos: TodosState; 47 | counter: CounterState; 48 | } 49 | 50 | 51 | const rootReducer: Reducer = combineReducers({ 52 | todos: todosReducer, 53 | counter: counterReducer, 54 | }) 55 | 56 | const rootState: RootState = rootReducer(undefined, { 57 | type: 'ADD_TODO', 58 | text: 'test', 59 | }) 60 | -------------------------------------------------------------------------------- /examples/todomvc/src/actions/index.spec.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | import * as actions from './index' 3 | 4 | describe('todo actions', () => { 5 | it('addTodo should create ADD_TODO action', () => { 6 | expect(actions.addTodo('Use Redux')).toEqual({ 7 | type: types.ADD_TODO, 8 | text: 'Use Redux' 9 | }) 10 | }) 11 | 12 | it('deleteTodo should create DELETE_TODO action', () => { 13 | expect(actions.deleteTodo(1)).toEqual({ 14 | type: types.DELETE_TODO, 15 | id: 1 16 | }) 17 | }) 18 | 19 | it('editTodo should create EDIT_TODO action', () => { 20 | expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({ 21 | type: types.EDIT_TODO, 22 | id: 1, 23 | text: 'Use Redux everywhere' 24 | }) 25 | }) 26 | 27 | it('completeTodo should create COMPLETE_TODO action', () => { 28 | expect(actions.completeTodo(1)).toEqual({ 29 | type: types.COMPLETE_TODO, 30 | id: 1 31 | }) 32 | }) 33 | 34 | it('completeAll should create COMPLETE_ALL action', () => { 35 | expect(actions.completeAll()).toEqual({ 36 | type: types.COMPLETE_ALL 37 | }) 38 | }) 39 | 40 | it('clearCompleted should create CLEAR_COMPLETED action', () => { 41 | expect(actions.clearCompleted()).toEqual({ 42 | type: types.CLEAR_COMPLETED 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/reducers/cart.spec.js: -------------------------------------------------------------------------------- 1 | import cart from './cart' 2 | 3 | describe('reducers', () => { 4 | describe('cart', () => { 5 | const initialState = { 6 | addedIds: [], 7 | quantityById: {} 8 | } 9 | 10 | it('should provide the initial state', () => { 11 | expect(cart(undefined, {})).toEqual(initialState) 12 | }) 13 | 14 | it('should handle CHECKOUT_REQUEST action', () => { 15 | expect(cart({}, { type: 'CHECKOUT_REQUEST' })).toEqual(initialState) 16 | }) 17 | 18 | it('should handle CHECKOUT_FAILURE action', () => { 19 | expect(cart({}, { type: 'CHECKOUT_FAILURE', cart: 'cart state' })).toEqual('cart state') 20 | }) 21 | 22 | it('should handle ADD_TO_CART action', () => { 23 | expect(cart(initialState, { type: 'ADD_TO_CART', productId: 1 })).toEqual({ 24 | addedIds: [ 1 ], 25 | quantityById: { 1: 1 } 26 | }) 27 | }) 28 | 29 | describe('when product is already in cart', () => { 30 | it('should handle ADD_TO_CART action', () => { 31 | const state = { 32 | addedIds: [ 1, 2 ], 33 | quantityById: { 1: 1, 2: 1 } 34 | } 35 | 36 | expect(cart(state, { type: 'ADD_TO_CART', productId: 2 })).toEqual({ 37 | addedIds: [ 1, 2 ], 38 | quantityById: { 1: 1, 2: 2 } 39 | }) 40 | }) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | import compose from './compose' 2 | 3 | /** 4 | * Creates a store enhancer that applies middleware to the dispatch method 5 | * of the Redux store. This is handy for a variety of tasks, such as expressing 6 | * asynchronous actions in a concise manner, or logging every action payload. 7 | * 8 | * See `redux-thunk` package as an example of the Redux middleware. 9 | * 10 | * Because middleware is potentially asynchronous, this should be the first 11 | * store enhancer in the composition chain. 12 | * 13 | * Note that each middleware will be given the `dispatch` and `getState` functions 14 | * as named arguments. 15 | * 16 | * @param {...Function} middlewares The middleware chain to be applied. 17 | * @returns {Function} A store enhancer applying the middleware. 18 | */ 19 | export default function applyMiddleware(...middlewares) { 20 | return (createStore) => (reducer, preloadedState, enhancer) => { 21 | var store = createStore(reducer, preloadedState, enhancer) 22 | var dispatch = store.dispatch 23 | var chain = [] 24 | 25 | var middlewareAPI = { 26 | getState: store.getState, 27 | dispatch: (action) => dispatch(action) 28 | } 29 | chain = middlewares.map(middleware => middleware(middlewareAPI)) 30 | dispatch = compose(...chain)(store.dispatch) 31 | 32 | return { 33 | ...store, 34 | dispatch 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/async/src/actions/index.js: -------------------------------------------------------------------------------- 1 | export const REQUEST_POSTS = 'REQUEST_POSTS' 2 | export const RECEIVE_POSTS = 'RECEIVE_POSTS' 3 | export const SELECT_REDDIT = 'SELECT_REDDIT' 4 | export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT' 5 | 6 | export const selectReddit = reddit => ({ 7 | type: SELECT_REDDIT, 8 | reddit 9 | }) 10 | 11 | export const invalidateReddit = reddit => ({ 12 | type: INVALIDATE_REDDIT, 13 | reddit 14 | }) 15 | 16 | export const requestPosts = reddit => ({ 17 | type: REQUEST_POSTS, 18 | reddit 19 | }) 20 | 21 | export const receivePosts = (reddit, json) => ({ 22 | type: RECEIVE_POSTS, 23 | reddit, 24 | posts: json.data.children.map(child => child.data), 25 | receivedAt: Date.now() 26 | }) 27 | 28 | const fetchPosts = reddit => dispatch => { 29 | dispatch(requestPosts(reddit)) 30 | return fetch(`https://www.reddit.com/r/${reddit}.json`) 31 | .then(response => response.json()) 32 | .then(json => dispatch(receivePosts(reddit, json))) 33 | } 34 | 35 | const shouldFetchPosts = (state, reddit) => { 36 | const posts = state.postsByReddit[reddit] 37 | if (!posts) { 38 | return true 39 | } 40 | if (posts.isFetching) { 41 | return false 42 | } 43 | return posts.didInvalidate 44 | } 45 | 46 | export const fetchPostsIfNeeded = reddit => (dispatch, getState) => { 47 | if (shouldFetchPosts(getState(), reddit)) { 48 | return dispatch(fetchPosts(reddit)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/reducers/products.spec.js: -------------------------------------------------------------------------------- 1 | import products from './products' 2 | 3 | describe('reducers', () => { 4 | describe('products', () => { 5 | it('should handle RECEIVE_PRODUCTS action', () => { 6 | const action = { 7 | type: 'RECEIVE_PRODUCTS', 8 | products: [ 9 | { 10 | id: 1, 11 | title: 'Product 1' 12 | }, 13 | { 14 | id: 2, 15 | title: 'Product 2' 16 | } 17 | ] 18 | } 19 | 20 | expect(products({}, action)).toEqual({ 21 | byId: { 22 | 1: { 23 | id: 1, 24 | title: 'Product 1' 25 | }, 26 | 2: { 27 | id: 2, 28 | title: 'Product 2' 29 | } 30 | }, 31 | visibleIds: [ 1, 2 ] 32 | }) 33 | }) 34 | 35 | it('should handle ADD_TO_CART action', () => { 36 | const state = { 37 | byId: { 38 | 1: { 39 | id: 1, 40 | title: 'Product 1', 41 | inventory: 1 42 | } 43 | } 44 | } 45 | 46 | expect(products(state, { type: 'ADD_TO_CART', productId: 1 })).toEqual({ 47 | byId: { 48 | 1: { 49 | id: 1, 50 | title: 'Product 1', 51 | inventory: 0 52 | } 53 | }, 54 | visibleIds: [] 55 | }) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /examples/todomvc/src/components/TodoTextInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import classnames from 'classnames' 3 | 4 | export default class TodoTextInput extends Component { 5 | static propTypes = { 6 | onSave: PropTypes.func.isRequired, 7 | text: PropTypes.string, 8 | placeholder: PropTypes.string, 9 | editing: PropTypes.bool, 10 | newTodo: PropTypes.bool 11 | } 12 | 13 | state = { 14 | text: this.props.text || '' 15 | } 16 | 17 | handleSubmit = e => { 18 | const text = e.target.value.trim() 19 | if (e.which === 13) { 20 | this.props.onSave(text) 21 | if (this.props.newTodo) { 22 | this.setState({ text: '' }) 23 | } 24 | } 25 | } 26 | 27 | handleChange = e => { 28 | this.setState({ text: e.target.value }) 29 | } 30 | 31 | handleBlur = e => { 32 | if (!this.props.newTodo) { 33 | this.props.onSave(e.target.value) 34 | } 35 | } 36 | 37 | render() { 38 | return ( 39 | 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/compose.spec.js: -------------------------------------------------------------------------------- 1 | import { compose } from '../src' 2 | 3 | describe('Utils', () => { 4 | describe('compose', () => { 5 | it('composes from right to left', () => { 6 | const double = x => x * 2 7 | const square = x => x * x 8 | expect(compose(square)(5)).toBe(25) 9 | expect(compose(square, double)(5)).toBe(100) 10 | expect(compose(double, square, double)(5)).toBe(200) 11 | }) 12 | 13 | it('composes functions from right to left', () => { 14 | const a = next => x => next(x + 'a') 15 | const b = next => x => next(x + 'b') 16 | const c = next => x => next(x + 'c') 17 | const final = x => x 18 | 19 | expect(compose(a, b, c)(final)('')).toBe('abc') 20 | expect(compose(b, c, a)(final)('')).toBe('bca') 21 | expect(compose(c, a, b)(final)('')).toBe('cab') 22 | }) 23 | 24 | it('can be seeded with multiple arguments', () => { 25 | const square = x => x * x 26 | const add = (x, y) => x + y 27 | expect(compose(square, add)(1, 2)).toBe(9) 28 | }) 29 | 30 | it('returns the first given argument if given no functions', () => { 31 | expect(compose()(1, 2)).toBe(1) 32 | expect(compose()(3)).toBe(3) 33 | expect(compose()()).toBe(undefined) 34 | }) 35 | 36 | it('returns the first function if given only one', () => { 37 | const fn = () => {} 38 | 39 | expect(compose(fn)).toBe(fn) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/reducers/products.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { RECEIVE_PRODUCTS, ADD_TO_CART } from '../constants/ActionTypes' 3 | 4 | const products = (state, action) => { 5 | switch (action.type) { 6 | case ADD_TO_CART: 7 | return { 8 | ...state, 9 | inventory: state.inventory - 1 10 | } 11 | default: 12 | return state 13 | } 14 | } 15 | 16 | const byId = (state = {}, action) => { 17 | switch (action.type) { 18 | case RECEIVE_PRODUCTS: 19 | return { 20 | ...state, 21 | ...action.products.reduce((obj, product) => { 22 | obj[product.id] = product 23 | return obj 24 | }, {}) 25 | } 26 | default: 27 | const { productId } = action 28 | if (productId) { 29 | return { 30 | ...state, 31 | [productId]: products(state[productId], action) 32 | } 33 | } 34 | return state 35 | } 36 | } 37 | 38 | const visibleIds = (state = [], action) => { 39 | switch (action.type) { 40 | case RECEIVE_PRODUCTS: 41 | return action.products.map(product => product.id) 42 | default: 43 | return state 44 | } 45 | } 46 | 47 | export default combineReducers({ 48 | byId, 49 | visibleIds 50 | }) 51 | 52 | export const getProduct = (state, id) => 53 | state.byId[id] 54 | 55 | export const getVisibleProducts = state => 56 | state.visibleIds.map(id => getProduct(state, id)) 57 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | The Redux API surface is tiny. Redux defines a set of contracts for you to implement (such as [reducers](../Glossary.md#reducer)) and provides a few helper functions to tie these contracts together. 4 | 5 | This section documents the complete Redux API. Keep in mind that Redux is only concerned with managing the state. In a real app, you'll also want to use UI bindings like [react-redux](https://github.com/gaearon/react-redux). 6 | 7 | ### Top-Level Exports 8 | 9 | * [createStore(reducer, [preloadedState], [enhancer])](createStore.md) 10 | * [combineReducers(reducers)](combineReducers.md) 11 | * [applyMiddleware(...middlewares)](applyMiddleware.md) 12 | * [bindActionCreators(actionCreators, dispatch)](bindActionCreators.md) 13 | * [compose(...functions)](compose.md) 14 | 15 | ### Store API 16 | 17 | * [Store](Store.md) 18 | * [getState()](Store.md#getState) 19 | * [dispatch(action)](Store.md#dispatch) 20 | * [subscribe(listener)](Store.md#subscribe) 21 | * [replaceReducer(nextReducer)](Store.md#replaceReducer) 22 | 23 | ### Importing 24 | 25 | Every function described above is a top-level export. You can import any of them like this: 26 | 27 | #### ES6 28 | 29 | ```js 30 | import { createStore } from 'redux' 31 | ``` 32 | 33 | #### ES5 (CommonJS) 34 | 35 | ```js 36 | var createStore = require('redux').createStore 37 | ``` 38 | 39 | #### ES5 (UMD build) 40 | 41 | ```js 42 | var createStore = Redux.createStore 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/reducers/cart.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_TO_CART, 3 | CHECKOUT_REQUEST, 4 | CHECKOUT_FAILURE 5 | } from '../constants/ActionTypes' 6 | 7 | const initialState = { 8 | addedIds: [], 9 | quantityById: {} 10 | } 11 | 12 | const addedIds = (state = initialState.addedIds, action) => { 13 | switch (action.type) { 14 | case ADD_TO_CART: 15 | if (state.indexOf(action.productId) !== -1) { 16 | return state 17 | } 18 | return [ ...state, action.productId ] 19 | default: 20 | return state 21 | } 22 | } 23 | 24 | const quantityById = (state = initialState.quantityById, action) => { 25 | switch (action.type) { 26 | case ADD_TO_CART: 27 | const { productId } = action 28 | return { ...state, 29 | [productId]: (state[productId] || 0) + 1 30 | } 31 | default: 32 | return state 33 | } 34 | } 35 | 36 | export const getQuantity = (state, productId) => 37 | state.quantityById[productId] || 0 38 | 39 | export const getAddedIds = state => state.addedIds 40 | 41 | const cart = (state = initialState, action) => { 42 | switch (action.type) { 43 | case CHECKOUT_REQUEST: 44 | return initialState 45 | case CHECKOUT_FAILURE: 46 | return action.cart 47 | default: 48 | return { 49 | addedIds: addedIds(state.addedIds, action), 50 | quantityById: quantityById(state.quantityById, action) 51 | } 52 | } 53 | } 54 | 55 | export default cart 56 | -------------------------------------------------------------------------------- /examples/todomvc/src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes' 2 | 3 | const initialState = [ 4 | { 5 | text: 'Use Redux', 6 | completed: false, 7 | id: 0 8 | } 9 | ] 10 | 11 | export default function todos(state = initialState, action) { 12 | switch (action.type) { 13 | case ADD_TODO: 14 | return [ 15 | { 16 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, 17 | completed: false, 18 | text: action.text 19 | }, 20 | ...state 21 | ] 22 | 23 | case DELETE_TODO: 24 | return state.filter(todo => 25 | todo.id !== action.id 26 | ) 27 | 28 | case EDIT_TODO: 29 | return state.map(todo => 30 | todo.id === action.id ? 31 | { ...todo, text: action.text } : 32 | todo 33 | ) 34 | 35 | case COMPLETE_TODO: 36 | return state.map(todo => 37 | todo.id === action.id ? 38 | { ...todo, completed: !todo.completed } : 39 | todo 40 | ) 41 | 42 | case COMPLETE_ALL: 43 | const areAllMarked = state.every(todo => todo.completed) 44 | return state.map(todo => ({ 45 | ...todo, 46 | completed: !areAllMarked 47 | })) 48 | 49 | case CLEAR_COMPLETED: 50 | return state.filter(todo => todo.completed === false) 51 | 52 | default: 53 | return state 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/advanced/AsyncFlow.md: -------------------------------------------------------------------------------- 1 | # Async Flow 2 | 3 | Without [middleware](Middleware.md), Redux store only supports [synchronous data flow](../basics/DataFlow.md). This is what you get by default with [`createStore()`](../api/createStore.md). 4 | 5 | You may enhance [`createStore()`](../api/createStore.md) with [`applyMiddleware()`](../api/applyMiddleware.md). It is not required, but it lets you [express asynchronous actions in a convenient way](AsyncActions.md). 6 | 7 | Asynchronous middleware like [redux-thunk](https://github.com/gaearon/redux-thunk) or [redux-promise](https://github.com/acdlite/redux-promise) wraps the store's [`dispatch()`](../api/Store.md#dispatch) method and allows you to dispatch something other than actions, for example, functions or Promises. Any middleware you use can then interpret anything you dispatch, and in turn, can pass actions to the next middleware in the chain. For example, a Promise middleware can intercept Promises and dispatch a pair of begin/end actions asynchronously in response to each Promise. 8 | 9 | When the last middleware in the chain dispatches an action, it has to be a plain object. This is when the [synchronous Redux data flow](../basics/DataFlow.md) takes place. 10 | 11 | Check out [the full source code for the async example](ExampleRedditAPI.md). 12 | 13 | ## Next Steps 14 | 15 | Now you saw an example of what middleware can do in Redux, it's time to learn how it actually works, and how you can create your own. Go on to the next detailed section about [Middleware](Middleware.md). 16 | -------------------------------------------------------------------------------- /examples/todomvc/src/components/Header.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TestUtils from 'react-addons-test-utils' 3 | import Header from './Header' 4 | import TodoTextInput from './TodoTextInput' 5 | 6 | const setup = () => { 7 | const props = { 8 | addTodo: jest.fn() 9 | } 10 | 11 | const renderer = TestUtils.createRenderer() 12 | renderer.render(

    ) 13 | const output = renderer.getRenderOutput() 14 | 15 | return { 16 | props: props, 17 | output: output, 18 | renderer: renderer 19 | } 20 | } 21 | 22 | describe('components', () => { 23 | describe('Header', () => { 24 | it('should render correctly', () => { 25 | const { output } = setup() 26 | 27 | expect(output.type).toBe('header') 28 | expect(output.props.className).toBe('header') 29 | 30 | const [ h1, input ] = output.props.children 31 | 32 | expect(h1.type).toBe('h1') 33 | expect(h1.props.children).toBe('todos') 34 | 35 | expect(input.type).toBe(TodoTextInput) 36 | expect(input.props.newTodo).toBe(true) 37 | expect(input.props.placeholder).toBe('What needs to be done?') 38 | }) 39 | 40 | it('should call addTodo if length of text is greater than 0', () => { 41 | const { output, props } = setup() 42 | const input = output.props.children[1] 43 | input.props.onSave('') 44 | expect(props.addTodo).not.toBeCalled() 45 | input.props.onSave('Use Redux') 46 | expect(props.addTodo).toBeCalled() 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /examples/real-world/src/components/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | export default class List extends Component { 4 | static propTypes = { 5 | loadingLabel: PropTypes.string.isRequired, 6 | pageCount: PropTypes.number, 7 | renderItem: PropTypes.func.isRequired, 8 | items: PropTypes.array.isRequired, 9 | isFetching: PropTypes.bool.isRequired, 10 | onLoadMoreClick: PropTypes.func.isRequired, 11 | nextPageUrl: PropTypes.string 12 | } 13 | 14 | static defaultProps = { 15 | isFetching: true, 16 | loadingLabel: 'Loading...' 17 | } 18 | 19 | renderLoadMore() { 20 | const { isFetching, onLoadMoreClick } = this.props 21 | return ( 22 | 27 | ) 28 | } 29 | 30 | render() { 31 | const { 32 | isFetching, nextPageUrl, pageCount, 33 | items, renderItem, loadingLabel 34 | } = this.props 35 | 36 | const isEmpty = items.length === 0 37 | if (isEmpty && isFetching) { 38 | return

    {loadingLabel}

    39 | } 40 | 41 | const isLastPage = !nextPageUrl 42 | if (isEmpty && isLastPage) { 43 | return

    Nothing here!

    44 | } 45 | 46 | return ( 47 |
    48 | {items.map(renderItem)} 49 | {pageCount > 0 && !isLastPage && this.renderLoadMore()} 50 |
    51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/async/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { 3 | SELECT_REDDIT, INVALIDATE_REDDIT, 4 | REQUEST_POSTS, RECEIVE_POSTS 5 | } from '../actions' 6 | 7 | const selectedReddit = (state = 'reactjs', action) => { 8 | switch (action.type) { 9 | case SELECT_REDDIT: 10 | return action.reddit 11 | default: 12 | return state 13 | } 14 | } 15 | 16 | const posts = (state = { 17 | isFetching: false, 18 | didInvalidate: false, 19 | items: [] 20 | }, action) => { 21 | switch (action.type) { 22 | case INVALIDATE_REDDIT: 23 | return { 24 | ...state, 25 | didInvalidate: true 26 | } 27 | case REQUEST_POSTS: 28 | return { 29 | ...state, 30 | isFetching: true, 31 | didInvalidate: false 32 | } 33 | case RECEIVE_POSTS: 34 | return { 35 | ...state, 36 | isFetching: false, 37 | didInvalidate: false, 38 | items: action.posts, 39 | lastUpdated: action.receivedAt 40 | } 41 | default: 42 | return state 43 | } 44 | } 45 | 46 | const postsByReddit = (state = { }, action) => { 47 | switch (action.type) { 48 | case INVALIDATE_REDDIT: 49 | case RECEIVE_POSTS: 50 | case REQUEST_POSTS: 51 | return { 52 | ...state, 53 | [action.reddit]: posts(state[action.reddit], action) 54 | } 55 | default: 56 | return state 57 | } 58 | } 59 | 60 | const rootReducer = combineReducers({ 61 | postsByReddit, 62 | selectedReddit 63 | }) 64 | 65 | export default rootReducer 66 | -------------------------------------------------------------------------------- /test/typescript/compose.ts: -------------------------------------------------------------------------------- 1 | import {compose} from "../../index.d.ts"; 2 | 3 | // copied from DefinitelyTyped/compose-function 4 | 5 | const numberToNumber = (a: number): number => a + 2; 6 | const numberToString = (a: number): string => "foo"; 7 | const stringToNumber = (a: string): number => 5; 8 | 9 | const t1: number = compose(numberToNumber, numberToNumber)(5); 10 | const t2: string = compose(numberToString, numberToNumber)(5); 11 | const t3: string = compose(numberToString, stringToNumber)("f"); 12 | const t4: (a: string) => number = compose( 13 | (f: (a: string) => number) => ((p: string) => 5), 14 | (f: (a: number) => string) => ((p: string) => 4) 15 | )(numberToString); 16 | 17 | 18 | const t5: number = compose(stringToNumber, numberToString, numberToNumber)(5); 19 | const t6: string = compose(numberToString, stringToNumber, numberToString, 20 | numberToNumber)(5); 21 | 22 | const t7: string = compose( 23 | numberToString, numberToNumber, stringToNumber, numberToString, 24 | stringToNumber)("fo"); 25 | 26 | 27 | const multiArgFn = (a: string, b: number, c: boolean): string => 'foo' 28 | 29 | const t8: string = compose(multiArgFn)('bar', 42, true); 30 | const t9: number = compose(stringToNumber, multiArgFn)('bar', 42, true); 31 | const t10: string = compose(numberToString, stringToNumber, 32 | multiArgFn)('bar', 42, true); 33 | 34 | const t11: number = compose(stringToNumber, numberToString, stringToNumber, 35 | multiArgFn)('bar', 42, true); 36 | 37 | 38 | const funcs = [stringToNumber, numberToString, stringToNumber]; 39 | const t12 = compose(...funcs)('bar', 42, true); 40 | -------------------------------------------------------------------------------- /docs/api/compose.md: -------------------------------------------------------------------------------- 1 | # `compose(...functions)` 2 | 3 | Composes functions from right to left. 4 | 5 | This is a functional programming utility, and is included in Redux as a convenience. 6 | You might want to use it to apply several [store enhancers](../Glossary.md#store-enhancer) in a row. 7 | 8 | #### Arguments 9 | 10 | 1. (*arguments*): The functions to compose. Each function is expected to accept a single parameter. Its return value will be provided as an argument to the function standing to the left, and so on. The exception is the right-most argument which can accept multiple parameters, as it will provide the signature for the resulting composed function. 11 | 12 | #### Returns 13 | 14 | (*Function*): The final function obtained by composing the given functions from right to left. 15 | 16 | #### Example 17 | 18 | This example demonstrates how to use `compose` to enhance a [store](Store.md) with [`applyMiddleware`](applyMiddleware.md) and a few developer tools from the [redux-devtools](https://github.com/gaearon/redux-devtools) package. 19 | 20 | ```js 21 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux' 22 | import thunk from 'redux-thunk' 23 | import DevTools from './containers/DevTools' 24 | import reducer from '../reducers/index' 25 | 26 | const store = createStore( 27 | reducer, 28 | compose( 29 | applyMiddleware(thunk), 30 | DevTools.instrument() 31 | ) 32 | ) 33 | ``` 34 | 35 | #### Tips 36 | 37 | * All `compose` does is let you write deeply nested function transformations without the rightward drift of the code. Don't give it too much credit! 38 | -------------------------------------------------------------------------------- /test/typescript/store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Store, createStore, Reducer, Action, StoreEnhancer, GenericStoreEnhancer, 3 | StoreCreator, StoreEnhancerStoreCreator, Unsubscribe 4 | } from "../../index.d.ts"; 5 | 6 | 7 | type State = { 8 | todos: string[]; 9 | } 10 | 11 | const reducer: Reducer = (state: State, action: Action): State => { 12 | return state; 13 | } 14 | 15 | 16 | /* createStore */ 17 | 18 | const store: Store = createStore(reducer); 19 | 20 | const storeWithPreloadedState: Store = createStore(reducer, { 21 | todos: [] 22 | }); 23 | 24 | const genericEnhancer: GenericStoreEnhancer = (next: StoreEnhancerStoreCreator) => next; 25 | const specificEnhencer: StoreEnhancer = next => next; 26 | 27 | const storeWithGenericEnhancer: Store = createStore(reducer, genericEnhancer); 28 | const storeWithSpecificEnhancer: Store = createStore(reducer, specificEnhencer); 29 | 30 | const storeWithPreloadedStateAndEnhancer: Store = createStore(reducer, { 31 | todos: [] 32 | }, genericEnhancer); 33 | 34 | 35 | /* dispatch */ 36 | 37 | store.dispatch({ 38 | type: 'ADD_TODO', 39 | text: 'test' 40 | }) 41 | 42 | 43 | /* getState */ 44 | 45 | const state: State = store.getState(); 46 | 47 | 48 | /* subscribe / unsubscribe */ 49 | 50 | const unsubscribe: Unsubscribe = store.subscribe(() => { 51 | console.log('Current state:', store.getState()) 52 | }) 53 | 54 | unsubscribe(); 55 | 56 | 57 | /* replaceReducer */ 58 | 59 | const newReducer: Reducer = (state: State, action: Action): State => { 60 | return state; 61 | } 62 | 63 | store.replaceReducer(newReducer); 64 | -------------------------------------------------------------------------------- /examples/real-world/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../actions' 2 | import merge from 'lodash/merge' 3 | import paginate from './paginate' 4 | import { routerReducer as routing } from 'react-router-redux' 5 | import { combineReducers } from 'redux' 6 | 7 | // Updates an entity cache in response to any action with response.entities. 8 | const entities = (state = { users: {}, repos: {} }, action) => { 9 | if (action.response && action.response.entities) { 10 | return merge({}, state, action.response.entities) 11 | } 12 | 13 | return state 14 | } 15 | 16 | // Updates error message to notify about the failed fetches. 17 | const errorMessage = (state = null, action) => { 18 | const { type, error } = action 19 | 20 | if (type === ActionTypes.RESET_ERROR_MESSAGE) { 21 | return null 22 | } else if (error) { 23 | return action.error 24 | } 25 | 26 | return state 27 | } 28 | 29 | // Updates the pagination data for different actions. 30 | const pagination = combineReducers({ 31 | starredByUser: paginate({ 32 | mapActionToKey: action => action.login, 33 | types: [ 34 | ActionTypes.STARRED_REQUEST, 35 | ActionTypes.STARRED_SUCCESS, 36 | ActionTypes.STARRED_FAILURE 37 | ] 38 | }), 39 | stargazersByRepo: paginate({ 40 | mapActionToKey: action => action.fullName, 41 | types: [ 42 | ActionTypes.STARGAZERS_REQUEST, 43 | ActionTypes.STARGAZERS_SUCCESS, 44 | ActionTypes.STARGAZERS_FAILURE 45 | ] 46 | }) 47 | }) 48 | 49 | const rootReducer = combineReducers({ 50 | entities, 51 | pagination, 52 | errorMessage, 53 | routing 54 | }) 55 | 56 | export default rootReducer 57 | -------------------------------------------------------------------------------- /examples/counter/README.md: -------------------------------------------------------------------------------- 1 | # Redux Counter Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | -------------------------------------------------------------------------------- /examples/todomvc/README.md: -------------------------------------------------------------------------------- 1 | # Redux TodoMVC Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | -------------------------------------------------------------------------------- /examples/async/README.md: -------------------------------------------------------------------------------- 1 | # Redux Async Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | 35 | -------------------------------------------------------------------------------- /examples/real-world/README.md: -------------------------------------------------------------------------------- 1 | # Redux Real World Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | -------------------------------------------------------------------------------- /examples/real-world/src/components/Explore.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | const GITHUB_REPO = 'https://github.com/reactjs/redux' 4 | 5 | export default class Explore extends Component { 6 | static propTypes = { 7 | value: PropTypes.string.isRequired, 8 | onChange: PropTypes.func.isRequired 9 | } 10 | 11 | componentWillReceiveProps(nextProps) { 12 | if (nextProps.value !== this.props.value) { 13 | this.setInputValue(nextProps.value) 14 | } 15 | } 16 | 17 | getInputValue = () => { 18 | return this.refs.input.value 19 | } 20 | 21 | setInputValue = (val) => { 22 | // Generally mutating DOM is a bad idea in React components, 23 | // but doing this for a single uncontrolled field is less fuss 24 | // than making it controlled and maintaining a state for it. 25 | this.refs.input.value = val 26 | } 27 | 28 | handleKeyUp = (e) => { 29 | if (e.keyCode === 13) { 30 | this.handleGoClick() 31 | } 32 | } 33 | 34 | handleGoClick = () => { 35 | this.props.onChange(this.getInputValue()) 36 | } 37 | 38 | render() { 39 | return ( 40 |
    41 |

    Type a username or repo full name and hit 'Go':

    42 | 46 | 49 |

    50 | Code on Github. 51 |

    52 |

    53 | Move the DevTools with Ctrl+W or hide them with Ctrl+H. 54 |

    55 |
    56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/todos/README.md: -------------------------------------------------------------------------------- 1 | # Redux Todos Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | 35 | -------------------------------------------------------------------------------- /examples/tree-view/README.md: -------------------------------------------------------------------------------- 1 | # Redux Tree View Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | -------------------------------------------------------------------------------- /examples/todos-flow/README.md: -------------------------------------------------------------------------------- 1 | # Redux Todos Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | 35 | -------------------------------------------------------------------------------- /examples/shopping-cart/README.md: -------------------------------------------------------------------------------- 1 | # Redux Shopping Cart Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | -------------------------------------------------------------------------------- /examples/todos-with-undo/README.md: -------------------------------------------------------------------------------- 1 | # Redux Todos with Undo Example 2 | 3 | This project template was built with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | 17 | ### `npm run build` 18 | 19 | Builds the app for production to the `build` folder.
    20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.
    23 | Your app is ready to be deployed! 24 | 25 | ### `npm run eject` 26 | 27 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 28 | 29 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 30 | 31 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 32 | 33 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 34 | 35 | -------------------------------------------------------------------------------- /examples/tree-view/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT, ADD_CHILD, REMOVE_CHILD, CREATE_NODE, DELETE_NODE } from '../actions' 2 | 3 | const childIds = (state, action) => { 4 | switch (action.type) { 5 | case ADD_CHILD: 6 | return [ ...state, action.childId ] 7 | case REMOVE_CHILD: 8 | return state.filter(id => id !== action.childId) 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | const node = (state, action) => { 15 | switch (action.type) { 16 | case CREATE_NODE: 17 | return { 18 | id: action.nodeId, 19 | counter: 0, 20 | childIds: [] 21 | } 22 | case INCREMENT: 23 | return { 24 | ...state, 25 | counter: state.counter + 1 26 | } 27 | case ADD_CHILD: 28 | case REMOVE_CHILD: 29 | return { 30 | ...state, 31 | childIds: childIds(state.childIds, action) 32 | } 33 | default: 34 | return state 35 | } 36 | } 37 | 38 | const getAllDescendantIds = (state, nodeId) => ( 39 | state[nodeId].childIds.reduce((acc, childId) => ( 40 | [ ...acc, childId, ...getAllDescendantIds(state, childId) ] 41 | ), []) 42 | ) 43 | 44 | const deleteMany = (state, ids) => { 45 | state = { ...state } 46 | ids.forEach(id => delete state[id]) 47 | return state 48 | } 49 | 50 | export default (state = {}, action) => { 51 | const { nodeId } = action 52 | if (typeof nodeId === 'undefined') { 53 | return state 54 | } 55 | 56 | if (action.type === DELETE_NODE) { 57 | const descendantIds = getAllDescendantIds(state, nodeId) 58 | return deleteMany(state, [ nodeId, ...descendantIds ]) 59 | } 60 | 61 | return { 62 | ...state, 63 | [nodeId]: node(state[nodeId], action) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/typescript/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionCreator, Action, Dispatch, 3 | bindActionCreators, ActionCreatorsMapObject 4 | } from "../../index.d.ts"; 5 | 6 | 7 | interface AddTodoAction extends Action { 8 | text: string; 9 | } 10 | 11 | const addTodo: ActionCreator = (text: string) => ({ 12 | type: 'ADD_TODO', 13 | text 14 | }) 15 | 16 | const addTodoAction: AddTodoAction = addTodo('test'); 17 | 18 | type AddTodoThunk = (dispatch: Dispatch) => AddTodoAction; 19 | 20 | const addTodoViaThunk: ActionCreator = (text: string) => 21 | (dispatch: Dispatch) => ({ 22 | type: 'ADD_TODO', 23 | text 24 | }) 25 | 26 | declare const dispatch: Dispatch; 27 | 28 | const boundAddTodo = bindActionCreators(addTodo, dispatch); 29 | 30 | const dispatchedAddTodoAction: AddTodoAction = boundAddTodo('test'); 31 | 32 | 33 | const boundAddTodoViaThunk = bindActionCreators< 34 | ActionCreator, 35 | ActionCreator 36 | >(addTodoViaThunk, dispatch) 37 | 38 | const dispatchedAddTodoViaThunkAction: AddTodoAction = 39 | boundAddTodoViaThunk('test'); 40 | 41 | 42 | const boundActionCreators = bindActionCreators({addTodo}, dispatch); 43 | 44 | const otherDispatchedAddTodoAction: AddTodoAction = 45 | boundActionCreators.addTodo('test'); 46 | 47 | 48 | interface M extends ActionCreatorsMapObject { 49 | addTodoViaThunk: ActionCreator 50 | } 51 | 52 | interface N extends ActionCreatorsMapObject { 53 | addTodoViaThunk: ActionCreator 54 | } 55 | 56 | const boundActionCreators2 = bindActionCreators({ 57 | addTodoViaThunk 58 | }, dispatch) 59 | 60 | const otherDispatchedAddTodoAction2: AddTodoAction = 61 | boundActionCreators2.addTodoViaThunk('test'); 62 | -------------------------------------------------------------------------------- /examples/real-world/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | import { browserHistory } from 'react-router' 4 | import Explore from '../components/Explore' 5 | import { resetErrorMessage } from '../actions' 6 | 7 | class App extends Component { 8 | static propTypes = { 9 | // Injected by React Redux 10 | errorMessage: PropTypes.string, 11 | resetErrorMessage: PropTypes.func.isRequired, 12 | inputValue: PropTypes.string.isRequired, 13 | // Injected by React Router 14 | children: PropTypes.node 15 | } 16 | 17 | handleDismissClick = e => { 18 | this.props.resetErrorMessage() 19 | e.preventDefault() 20 | } 21 | 22 | handleChange = nextValue => { 23 | browserHistory.push(`/${nextValue}`) 24 | } 25 | 26 | renderErrorMessage() { 27 | const { errorMessage } = this.props 28 | if (!errorMessage) { 29 | return null 30 | } 31 | 32 | return ( 33 |

    34 | {errorMessage} 35 | {' '} 36 | ( 38 | Dismiss 39 | ) 40 |

    41 | ) 42 | } 43 | 44 | render() { 45 | const { children, inputValue } = this.props 46 | return ( 47 |
    48 | 50 |
    51 | {this.renderErrorMessage()} 52 | {children} 53 |
    54 | ) 55 | } 56 | } 57 | 58 | const mapStateToProps = (state, ownProps) => ({ 59 | errorMessage: state.errorMessage, 60 | inputValue: ownProps.location.pathname.substring(1) 61 | }) 62 | 63 | export default connect(mapStateToProps, { 64 | resetErrorMessage 65 | })(App) 66 | -------------------------------------------------------------------------------- /examples/todomvc/src/components/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import classnames from 'classnames' 3 | import TodoTextInput from './TodoTextInput' 4 | 5 | export default class TodoItem extends Component { 6 | static propTypes = { 7 | todo: PropTypes.object.isRequired, 8 | editTodo: PropTypes.func.isRequired, 9 | deleteTodo: PropTypes.func.isRequired, 10 | completeTodo: PropTypes.func.isRequired 11 | } 12 | 13 | state = { 14 | editing: false 15 | } 16 | 17 | handleDoubleClick = () => { 18 | this.setState({ editing: true }) 19 | } 20 | 21 | handleSave = (id, text) => { 22 | if (text.length === 0) { 23 | this.props.deleteTodo(id) 24 | } else { 25 | this.props.editTodo(id, text) 26 | } 27 | this.setState({ editing: false }) 28 | } 29 | 30 | render() { 31 | const { todo, completeTodo, deleteTodo } = this.props 32 | 33 | let element 34 | if (this.state.editing) { 35 | element = ( 36 | this.handleSave(todo.id, text)} /> 39 | ) 40 | } else { 41 | element = ( 42 |
    43 | completeTodo(todo.id)} /> 47 | 50 |
    53 | ) 54 | } 55 | 56 | return ( 57 |
  • 61 | {element} 62 |
  • 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/typescript/middleware.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Middleware, MiddlewareAPI, 3 | applyMiddleware, createStore, Dispatch, Reducer, Action 4 | } from "../../index.d.ts"; 5 | 6 | declare module "../../index.d.ts" { 7 | export interface Dispatch { 8 | (asyncAction: (dispatch: Dispatch, getState: () => S) => R): R; 9 | } 10 | } 11 | 12 | type Thunk = (dispatch: Dispatch, getState: () => S) => O; 13 | 14 | const thunkMiddleware: Middleware = 15 | ({dispatch, getState}: MiddlewareAPI) => 16 | (next: Dispatch) => 17 | (action: A | Thunk): B|Action => 18 | typeof action === 'function' ? 19 | (>action)(dispatch, getState) : 20 | next(action) 21 | 22 | 23 | const loggerMiddleware: Middleware = 24 | ({getState}: MiddlewareAPI) => 25 | (next: Dispatch) => 26 | (action: any): any => { 27 | console.log('will dispatch', action) 28 | 29 | // Call the next dispatch method in the middleware chain. 30 | const returnValue = next(action) 31 | 32 | console.log('state after dispatch', getState()) 33 | 34 | // This will likely be the action itself, unless 35 | // a middleware further in chain changed it. 36 | return returnValue 37 | } 38 | 39 | 40 | 41 | type State = { 42 | todos: string[]; 43 | } 44 | 45 | const reducer: Reducer = (state: State, action: Action): State => { 46 | return state; 47 | } 48 | 49 | const storeWithThunkMiddleware = createStore( 50 | reducer, 51 | applyMiddleware(thunkMiddleware) 52 | ); 53 | 54 | storeWithThunkMiddleware.dispatch( 55 | (dispatch, getState) => { 56 | const todos: string[] = getState().todos; 57 | dispatch({type: 'ADD_TODO'}) 58 | } 59 | ) 60 | 61 | 62 | const storeWithMultipleMiddleware = createStore( 63 | reducer, 64 | applyMiddleware(loggerMiddleware, thunkMiddleware) 65 | ) 66 | -------------------------------------------------------------------------------- /examples/tree-view/src/containers/Node.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Component } from 'react' 3 | import { connect } from 'react-redux' 4 | import * as actions from '../actions' 5 | 6 | export class Node extends Component { 7 | handleIncrementClick = () => { 8 | const { increment, id } = this.props 9 | increment(id) 10 | } 11 | 12 | handleAddChildClick = e => { 13 | e.preventDefault() 14 | 15 | const { addChild, createNode, id } = this.props 16 | const childId = createNode().nodeId 17 | addChild(id, childId) 18 | } 19 | 20 | handleRemoveClick = e => { 21 | e.preventDefault() 22 | 23 | const { removeChild, deleteNode, parentId, id } = this.props 24 | removeChild(parentId, id) 25 | deleteNode(id) 26 | } 27 | 28 | renderChild = childId => { 29 | const { id } = this.props 30 | return ( 31 |
  • 32 | 33 |
  • 34 | ) 35 | } 36 | 37 | render() { 38 | const { counter, parentId, childIds } = this.props 39 | return ( 40 |
    62 | ) 63 | } 64 | } 65 | 66 | function mapStateToProps(state, ownProps) { 67 | return state[ownProps.id] 68 | } 69 | 70 | const ConnectedNode = connect(mapStateToProps, actions)(Node) 71 | export default ConnectedNode 72 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/reducers/index.spec.js: -------------------------------------------------------------------------------- 1 | import { getTotal, getCartProducts } from './index' 2 | 3 | describe('selectors', () => { 4 | describe('getTotal', () => { 5 | it('should return price total', () => { 6 | const state = { 7 | cart: { 8 | addedIds: [ 1, 2, 3 ], 9 | quantityById: { 10 | 1: 4, 11 | 2: 2, 12 | 3: 1 13 | } 14 | }, 15 | products: { 16 | byId: { 17 | 1: { 18 | id: 1, 19 | price: 1.99 20 | }, 21 | 2: { 22 | id: 1, 23 | price: 4.99 24 | }, 25 | 3: { 26 | id: 1, 27 | price: 9.99 28 | } 29 | } 30 | } 31 | } 32 | expect(getTotal(state)).toBe('27.93') 33 | }) 34 | }) 35 | 36 | describe('getCartProducts', () => { 37 | it('should return products with quantity', () => { 38 | const state = { 39 | cart: { 40 | addedIds: [ 1, 2, 3 ], 41 | quantityById: { 42 | 1: 4, 43 | 2: 2, 44 | 3: 1 45 | } 46 | }, 47 | products: { 48 | byId: { 49 | 1: { 50 | id: 1, 51 | price: 1.99 52 | }, 53 | 2: { 54 | id: 1, 55 | price: 4.99 56 | }, 57 | 3: { 58 | id: 1, 59 | price: 9.99 60 | } 61 | } 62 | } 63 | } 64 | 65 | expect(getCartProducts(state)).toEqual([ 66 | { 67 | id: 1, 68 | price: 1.99, 69 | quantity: 4 70 | }, 71 | { 72 | id: 1, 73 | price: 4.99, 74 | quantity: 2 75 | }, 76 | { 77 | id: 1, 78 | price: 9.99, 79 | quantity: 1 80 | } 81 | ]) 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /logo/README.md: -------------------------------------------------------------------------------- 1 | # The Redux Logo 2 | 3 | This is the only official Redux logo. 4 | Don't use any other logos to represent Redux. 5 | 6 | It comes in several flavors. 7 | 8 | ## Just the Logo 9 | 10 | Redux Logo 11 | 12 | Download as [PNG](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo.png) or [SVG](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo.svg). 13 | 14 | ## Logo with a Dark Title 15 | 16 | Redux Logo with Dark Title 17 | 18 | Download as [PNG](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo-title-dark.png). 19 | 20 | ## Logo with a Light Title 21 | 22 | Redux Logo with Light Title 23 | 24 | *(You can't see the text but it's there, in white.)* 25 | 26 | Download as [PNG](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo-title-light.png). 27 | 28 | ## Modifications 29 | 30 | Whenever possible, we ask you to use the originals provided on this page. 31 | 32 | If for some reason you must change how the title is rendered and can't use the prerendered version we provide, we ask that you use the free [Lato Black](http://www.latofonts.com/lato-free-fonts/) font and ensure there is enough space between the logo and the title. 33 | 34 | When in doubt, use the original logos. 35 | 36 | ## Credits 37 | 38 | The Redux logo was designed by [Matthew Johnston](http://thedeskofmatthew.com/). 39 | Thanks to [Jay Phelps](https://github.com/jayphelps) and [the community](https://github.com/reactjs/redux/issues/151) for contributing earlier proposals. 40 | 41 | ## License 42 | 43 | The Redux logo is licensed under CC0, waiving all copyright. 44 | [Read the license.](../LICENSE-logo.md) 45 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/ProductItem.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import Product from './Product' 4 | import ProductItem from './ProductItem' 5 | 6 | const setup = product => { 7 | const actions = { 8 | onAddToCartClicked: jest.fn() 9 | } 10 | 11 | const component = shallow( 12 | 13 | ) 14 | 15 | return { 16 | component: component, 17 | actions: actions, 18 | button: component.find('button'), 19 | product: component.find(Product) 20 | } 21 | } 22 | 23 | let productProps 24 | 25 | describe('ProductItem component', () => { 26 | beforeEach(() => { 27 | productProps = { 28 | title: 'Product 1', 29 | price: 9.99, 30 | inventory: 6 31 | } 32 | }) 33 | 34 | it('should render product', () => { 35 | const { product } = setup(productProps) 36 | expect(product.props()).toEqual({ title: 'Product 1', price: 9.99 }) 37 | }) 38 | 39 | it('should render Add To Cart message', () => { 40 | const { button } = setup(productProps) 41 | expect(button.text()).toMatch(/^Add to cart/) 42 | }) 43 | 44 | it('should not disable button', () => { 45 | const { button } = setup(productProps) 46 | expect(button.prop('disabled')).toEqual('') 47 | }) 48 | 49 | it('should call action on button click', () => { 50 | const { button, actions } = setup(productProps) 51 | button.simulate('click') 52 | expect(actions.onAddToCartClicked).toBeCalled() 53 | }) 54 | 55 | describe('when product inventory is 0', () => { 56 | beforeEach(() => { 57 | productProps.inventory = 0 58 | }) 59 | 60 | it('should render Sold Out message', () => { 61 | const { button } = setup(productProps) 62 | expect(button.text()).toMatch(/^Sold Out/) 63 | }) 64 | 65 | it('should disable button', () => { 66 | const { button } = setup(productProps) 67 | expect(button.prop('disabled')).toEqual('disabled') 68 | }) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /examples/real-world/src/reducers/paginate.js: -------------------------------------------------------------------------------- 1 | import union from 'lodash/union' 2 | 3 | // Creates a reducer managing pagination, given the action types to handle, 4 | // and a function telling how to extract the key from an action. 5 | const paginate = ({ types, mapActionToKey }) => { 6 | if (!Array.isArray(types) || types.length !== 3) { 7 | throw new Error('Expected types to be an array of three elements.') 8 | } 9 | if (!types.every(t => typeof t === 'string')) { 10 | throw new Error('Expected types to be strings.') 11 | } 12 | if (typeof mapActionToKey !== 'function') { 13 | throw new Error('Expected mapActionToKey to be a function.') 14 | } 15 | 16 | const [ requestType, successType, failureType ] = types 17 | 18 | const updatePagination = (state = { 19 | isFetching: false, 20 | nextPageUrl: undefined, 21 | pageCount: 0, 22 | ids: [] 23 | }, action) => { 24 | switch (action.type) { 25 | case requestType: 26 | return { 27 | ...state, 28 | isFetching: true 29 | } 30 | case successType: 31 | return { 32 | ...state, 33 | isFetching: false, 34 | ids: union(state.ids, action.response.result), 35 | nextPageUrl: action.response.nextPageUrl, 36 | pageCount: state.pageCount + 1 37 | } 38 | case failureType: 39 | return { 40 | ...state, 41 | isFetching: false 42 | } 43 | default: 44 | return state 45 | } 46 | } 47 | 48 | return (state = {}, action) => { 49 | // Update pagination by key 50 | switch (action.type) { 51 | case requestType: 52 | case successType: 53 | case failureType: 54 | const key = mapActionToKey(action) 55 | if (typeof key !== 'string') { 56 | throw new Error('Expected key to be a string.') 57 | } 58 | return { ...state, 59 | [key]: updatePagination(state[key], action) 60 | } 61 | default: 62 | return state 63 | } 64 | } 65 | } 66 | 67 | export default paginate 68 | -------------------------------------------------------------------------------- /examples/counter-vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redux basic example 5 | 6 | 7 | 8 |
    9 |

    10 | Clicked: 0 times 11 | 12 | 13 | 14 | 15 |

    16 |
    17 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/shopping-cart/src/components/Cart.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import Cart from './Cart' 4 | import Product from './Product' 5 | 6 | const setup = (total, products = []) => { 7 | const actions = { 8 | onCheckoutClicked: jest.fn() 9 | } 10 | 11 | const component = shallow( 12 | 13 | ) 14 | 15 | return { 16 | component: component, 17 | actions: actions, 18 | button: component.find('button'), 19 | products: component.find(Product), 20 | em: component.find('em'), 21 | p: component.find('p') 22 | } 23 | } 24 | 25 | describe('Cart component', () => { 26 | it('should display total', () => { 27 | const { p } = setup('76') 28 | expect(p.text()).toMatch(/^Total: \$76/) 29 | }) 30 | 31 | it('should display add some products message', () => { 32 | const { em } = setup() 33 | expect(em.text()).toMatch(/^Please add some products to cart/) 34 | }) 35 | 36 | it('should disable button', () => { 37 | const { button } = setup() 38 | expect(button.prop('disabled')).toEqual('disabled') 39 | }) 40 | 41 | describe('when given product', () => { 42 | const product = [ 43 | { 44 | id: 1, 45 | title: 'Product 1', 46 | price: 9.99, 47 | quantity: 1 48 | } 49 | ] 50 | 51 | it('should render products', () => { 52 | const { products } = setup('9.99', product) 53 | const props = { 54 | title: product[0].title, 55 | price: product[0].price, 56 | quantity: product[0].quantity 57 | } 58 | 59 | expect(products.at(0).props()).toEqual(props) 60 | }) 61 | 62 | it('should not disable button', () => { 63 | const { button } = setup('9.99', product) 64 | expect(button.prop('disabled')).toEqual('') 65 | }) 66 | 67 | it('should call action on button click', () => { 68 | const { button, actions } = setup('9.99', product) 69 | button.simulate('click') 70 | expect(actions.onCheckoutClicked).toBeCalled() 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /examples/counter/src/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import Counter from './Counter' 4 | 5 | function setup(value = 0) { 6 | const actions = { 7 | onIncrement: jest.fn(), 8 | onDecrement: jest.fn() 9 | } 10 | const component = shallow( 11 | 12 | ) 13 | 14 | return { 15 | component: component, 16 | actions: actions, 17 | buttons: component.find('button'), 18 | p: component.find('p') 19 | } 20 | } 21 | 22 | describe('Counter component', () => { 23 | it('should display count', () => { 24 | const { p } = setup() 25 | expect(p.text()).toMatch(/^Clicked: 0 times/) 26 | }) 27 | 28 | it('first button should call onIncrement', () => { 29 | const { buttons, actions } = setup() 30 | buttons.at(0).simulate('click') 31 | expect(actions.onIncrement).toBeCalled() 32 | }) 33 | 34 | it('second button should call onDecrement', () => { 35 | const { buttons, actions } = setup() 36 | buttons.at(1).simulate('click') 37 | expect(actions.onDecrement).toBeCalled() 38 | }) 39 | 40 | it('third button should not call onIncrement if the counter is even', () => { 41 | const { buttons, actions } = setup(42) 42 | buttons.at(2).simulate('click') 43 | expect(actions.onIncrement).not.toBeCalled() 44 | }) 45 | 46 | it('third button should call onIncrement if the counter is odd', () => { 47 | const { buttons, actions } = setup(43) 48 | buttons.at(2).simulate('click') 49 | expect(actions.onIncrement).toBeCalled() 50 | }) 51 | 52 | it('third button should call onIncrement if the counter is odd and negative', () => { 53 | const { buttons, actions } = setup(-43) 54 | buttons.at(2).simulate('click') 55 | expect(actions.onIncrement).toBeCalled() 56 | }) 57 | 58 | it('fourth button should call onIncrement in a second', (done) => { 59 | const { buttons, actions } = setup() 60 | buttons.at(3).simulate('click') 61 | setTimeout(() => { 62 | expect(actions.onIncrement).toBeCalled() 63 | done() 64 | }, 1000) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /src/bindActionCreators.js: -------------------------------------------------------------------------------- 1 | function bindActionCreator(actionCreator, dispatch) { 2 | return (...args) => dispatch(actionCreator(...args)) 3 | } 4 | 5 | /** 6 | * Turns an object whose values are action creators, into an object with the 7 | * same keys, but with every function wrapped into a `dispatch` call so they 8 | * may be invoked directly. This is just a convenience method, as you can call 9 | * `store.dispatch(MyActionCreators.doSomething())` yourself just fine. 10 | * 11 | * For convenience, you can also pass a single function as the first argument, 12 | * and get a function in return. 13 | * 14 | * @param {Function|Object} actionCreators An object whose values are action 15 | * creator functions. One handy way to obtain it is to use ES6 `import * as` 16 | * syntax. You may also pass a single function. 17 | * 18 | * @param {Function} dispatch The `dispatch` function available on your Redux 19 | * store. 20 | * 21 | * @returns {Function|Object} The object mimicking the original object, but with 22 | * every action creator wrapped into the `dispatch` call. If you passed a 23 | * function as `actionCreators`, the return value will also be a single 24 | * function. 25 | */ 26 | export default function bindActionCreators(actionCreators, dispatch) { 27 | if (typeof actionCreators === 'function') { 28 | return bindActionCreator(actionCreators, dispatch) 29 | } 30 | 31 | if (typeof actionCreators !== 'object' || actionCreators === null) { 32 | throw new Error( 33 | `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + 34 | `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` 35 | ) 36 | } 37 | 38 | var keys = Object.keys(actionCreators) 39 | var boundActionCreators = {} 40 | for (var i = 0; i < keys.length; i++) { 41 | var key = keys[i] 42 | var actionCreator = actionCreators[key] 43 | if (typeof actionCreator === 'function') { 44 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) 45 | } 46 | } 47 | return boundActionCreators 48 | } 49 | -------------------------------------------------------------------------------- /examples/todomvc/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import classnames from 'classnames' 3 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' 4 | 5 | const FILTER_TITLES = { 6 | [SHOW_ALL]: 'All', 7 | [SHOW_ACTIVE]: 'Active', 8 | [SHOW_COMPLETED]: 'Completed' 9 | } 10 | 11 | export default class Footer extends Component { 12 | static propTypes = { 13 | completedCount: PropTypes.number.isRequired, 14 | activeCount: PropTypes.number.isRequired, 15 | filter: PropTypes.string.isRequired, 16 | onClearCompleted: PropTypes.func.isRequired, 17 | onShow: PropTypes.func.isRequired 18 | } 19 | 20 | renderTodoCount() { 21 | const { activeCount } = this.props 22 | const itemWord = activeCount === 1 ? 'item' : 'items' 23 | 24 | return ( 25 | 26 | {activeCount || 'No'} {itemWord} left 27 | 28 | ) 29 | } 30 | 31 | renderFilterLink(filter) { 32 | const title = FILTER_TITLES[filter] 33 | const { filter: selectedFilter, onShow } = this.props 34 | 35 | return ( 36 | onShow(filter)}> 39 | {title} 40 | 41 | ) 42 | } 43 | 44 | renderClearButton() { 45 | const { completedCount, onClearCompleted } = this.props 46 | if (completedCount > 0) { 47 | return ( 48 | 52 | ) 53 | } 54 | } 55 | 56 | render() { 57 | return ( 58 |
    59 | {this.renderTodoCount()} 60 |
      61 | {[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter => 62 |
    • 63 | {this.renderFilterLink(filter)} 64 |
    • 65 | )} 66 |
    67 | {this.renderClearButton()} 68 |
    69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /flow-typed/redux.js: -------------------------------------------------------------------------------- 1 | declare module 'redux' { 2 | 3 | /* 4 | 5 | S = State 6 | A = Action 7 | 8 | */ 9 | 10 | declare type Dispatch }> = (action: A) => A; 11 | 12 | declare type MiddlewareAPI = { 13 | dispatch: Dispatch; 14 | getState(): S; 15 | }; 16 | 17 | declare type Store = { 18 | // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages) 19 | dispatch: Dispatch; 20 | getState(): S; 21 | subscribe(listener: () => void): () => void; 22 | replaceReducer(nextReducer: Reducer): void 23 | }; 24 | 25 | declare type Reducer = (state: S, action: A) => S; 26 | 27 | declare type Middleware = 28 | (api: MiddlewareAPI) => 29 | (next: Dispatch) => Dispatch; 30 | 31 | declare type StoreCreator = { 32 | (reducer: Reducer, enhancer?: StoreEnhancer): Store; 33 | (reducer: Reducer, preloadedState: S, enhancer?: StoreEnhancer): Store; 34 | }; 35 | 36 | declare type StoreEnhancer = (next: StoreCreator) => StoreCreator; 37 | 38 | declare function createStore(reducer: Reducer, enhancer?: StoreEnhancer): Store; 39 | declare function createStore(reducer: Reducer, preloadedState: S, enhancer?: StoreEnhancer): Store; 40 | 41 | declare function applyMiddleware(...middlewares: Array>): StoreEnhancer; 42 | 43 | declare type ActionCreator = (...args: Array) => A; 44 | declare type ActionCreators = { [key: K]: ActionCreator }; 45 | 46 | declare function bindActionCreators>(actionCreator: C, dispatch: Dispatch): C; 47 | declare function bindActionCreators>(actionCreators: C, dispatch: Dispatch): C; 48 | 49 | // unsafe (you can miss a field and / or assign a wrong reducer to a field) 50 | declare function combineReducers(reducers: {[key: $Keys]: Reducer}): Reducer; 51 | 52 | declare function compose(...fns: Array>): Function; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /docs/introduction/Motivation.md: -------------------------------------------------------------------------------- 1 | # Motivation 2 | 3 | As the requirements for JavaScript single-page applications have become increasingly complicated, **our code must manage more state than ever before**. This state can include server responses and cached data, as well as locally created data that has not yet been persisted to the server. UI state is also increasing in complexity, as we need to manage the active route, the selected tab, whether to show a spinner or not, should pagination controls be displayed, and so on. 4 | 5 | Managing this ever-changing state is hard. If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. At some point, you no longer understand what happens in your app as you have **lost control over the when, why, and how of its state.** When a system is opaque and non-deterministic, it's hard to reproduce bugs or add new features. 6 | 7 | As if this wasn't bad enough, consider the **new requirements becoming common in front-end product development**. As developers, we are expected to handle optimistic updates, server-side rendering, fetching data before performing route transitions, and so on. We find ourselves trying to manage a complexity that we have never had to deal with before, and we inevitably ask the question: [is it time to give up?](http://www.quirksmode.org/blog/archives/2015/07/stop_pushing_th.html) The answer is _no_. 8 | 9 | This complexity is difficult to handle as **we're mixing two concepts** that are very hard for the human mind to reason about: **mutation and asynchronicity.** I call them [Mentos and Coke](https://en.wikipedia.org/wiki/Diet_Coke_and_Mentos_eruption). Both can be great in separation, but together they create a mess. Libraries like [React](http://facebook.github.io/react) attempt to solve this problem in the view layer by removing both asynchrony and direct DOM manipulation. However, managing the state of your data is left up to you. This is where Redux enters. 10 | 11 | Following in the steps of [Flux](http://facebook.github.io/flux), [CQRS](http://martinfowler.com/bliki/CQRS.html), and [Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html), **Redux attempts to make state mutations predictable** by imposing certain restrictions on how and when updates can happen. These restrictions are reflected in the [three principles](ThreePrinciples.md) of Redux. 12 | -------------------------------------------------------------------------------- /examples/todomvc/src/components/MainSection.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import TodoItem from './TodoItem' 3 | import Footer from './Footer' 4 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' 5 | 6 | const TODO_FILTERS = { 7 | [SHOW_ALL]: () => true, 8 | [SHOW_ACTIVE]: todo => !todo.completed, 9 | [SHOW_COMPLETED]: todo => todo.completed 10 | } 11 | 12 | export default class MainSection extends Component { 13 | static propTypes = { 14 | todos: PropTypes.array.isRequired, 15 | actions: PropTypes.object.isRequired 16 | } 17 | 18 | state = { filter: SHOW_ALL } 19 | 20 | handleClearCompleted = () => { 21 | this.props.actions.clearCompleted() 22 | } 23 | 24 | handleShow = filter => { 25 | this.setState({ filter }) 26 | } 27 | 28 | renderToggleAll(completedCount) { 29 | const { todos, actions } = this.props 30 | if (todos.length > 0) { 31 | return ( 32 | 36 | ) 37 | } 38 | } 39 | 40 | renderFooter(completedCount) { 41 | const { todos } = this.props 42 | const { filter } = this.state 43 | const activeCount = todos.length - completedCount 44 | 45 | if (todos.length) { 46 | return ( 47 |