diff --git a/docs/examples/todos/LICENSE.md b/docs/examples/todos/LICENSE.md new file mode 100644 index 0000000..55bc8df --- /dev/null +++ b/docs/examples/todos/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/examples/todos/README.md b/docs/examples/todos/README.md new file mode 100644 index 0000000..35a04a1 --- /dev/null +++ b/docs/examples/todos/README.md @@ -0,0 +1,2 @@ +Code in this folder is borrowed from https://github.com/reduxjs/redux/tree/master/examples/todos +See [LICENSE.md](./LICENSE.md) for license diff --git a/docs/examples/todos/public/index.html b/docs/examples/todos/public/index.html new file mode 100644 index 0000000..2e474bc --- /dev/null +++ b/docs/examples/todos/public/index.html @@ -0,0 +1,21 @@ + + + + + + Redux Todos Example + + +
+ + + diff --git a/docs/examples/todos/src/actions/index.js b/docs/examples/todos/src/actions/index.js new file mode 100644 index 0000000..d10bb7a --- /dev/null +++ b/docs/examples/todos/src/actions/index.js @@ -0,0 +1,22 @@ +let nextTodoId = 0 +export const addTodo = text => ({ + type: 'ADD_TODO', + id: nextTodoId++, + text +}) + +export const setVisibilityFilter = filter => ({ + type: 'SET_VISIBILITY_FILTER', + filter +}) + +export const toggleTodo = id => ({ + type: 'TOGGLE_TODO', + id +}) + +export const VisibilityFilters = { + SHOW_ALL: 'SHOW_ALL', + SHOW_COMPLETED: 'SHOW_COMPLETED', + SHOW_ACTIVE: 'SHOW_ACTIVE' +} diff --git a/docs/examples/todos/src/actions/index.spec.js b/docs/examples/todos/src/actions/index.spec.js new file mode 100644 index 0000000..3759659 --- /dev/null +++ b/docs/examples/todos/src/actions/index.spec.js @@ -0,0 +1,25 @@ +import * as actions from './index' + +describe('todo actions', () => { + it('addTodo should create ADD_TODO action', () => { + expect(actions.addTodo('Use Redux')).toEqual({ + type: 'ADD_TODO', + id: 0, + text: 'Use Redux' + }) + }) + + it('setVisibilityFilter should create SET_VISIBILITY_FILTER action', () => { + expect(actions.setVisibilityFilter('active')).toEqual({ + type: 'SET_VISIBILITY_FILTER', + filter: 'active' + }) + }) + + it('toggleTodo should create TOGGLE_TODO action', () => { + expect(actions.toggleTodo(1)).toEqual({ + type: 'TOGGLE_TODO', + id: 1 + }) + }) +}) diff --git a/docs/examples/todos/src/components/App.js b/docs/examples/todos/src/components/App.js new file mode 100644 index 0000000..6289393 --- /dev/null +++ b/docs/examples/todos/src/components/App.js @@ -0,0 +1,14 @@ +import React from 'react' +import Footer from './Footer' +import AddTodo from '../containers/AddTodo' +import VisibleTodoList from '../containers/VisibleTodoList' + +const App = () => ( +
+ + +
+) + +export default App diff --git a/docs/examples/todos/src/components/Footer.js b/docs/examples/todos/src/components/Footer.js new file mode 100644 index 0000000..60198e8 --- /dev/null +++ b/docs/examples/todos/src/components/Footer.js @@ -0,0 +1,20 @@ +import React from 'react' +import FilterLink from '../containers/FilterLink' +import { VisibilityFilters } from '../actions' + +const Footer = () => ( +
+ Show: + + All + + + Active + + + Completed + +
+) + +export default Footer diff --git a/docs/examples/todos/src/components/Link.js b/docs/examples/todos/src/components/Link.js new file mode 100644 index 0000000..9ae6af4 --- /dev/null +++ b/docs/examples/todos/src/components/Link.js @@ -0,0 +1,22 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const Link = ({ active, children, onClick }) => ( + +) + +Link.propTypes = { + active: PropTypes.bool.isRequired, + children: PropTypes.node.isRequired, + onClick: PropTypes.func.isRequired +} + +export default Link diff --git a/docs/examples/todos/src/components/Todo.js b/docs/examples/todos/src/components/Todo.js new file mode 100644 index 0000000..09b5b40 --- /dev/null +++ b/docs/examples/todos/src/components/Todo.js @@ -0,0 +1,21 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const Todo = ({ onClick, completed, text }) => ( +
  • + {text} +
  • +) + +Todo.propTypes = { + onClick: PropTypes.func.isRequired, + completed: PropTypes.bool.isRequired, + text: PropTypes.string.isRequired +} + +export default Todo diff --git a/docs/examples/todos/src/components/TodoList.js b/docs/examples/todos/src/components/TodoList.js new file mode 100644 index 0000000..7591db6 --- /dev/null +++ b/docs/examples/todos/src/components/TodoList.js @@ -0,0 +1,26 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Todo from './Todo' + +const TodoList = ({ todos, toggleTodo }) => ( + +) + +TodoList.propTypes = { + todos: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number.isRequired, + completed: PropTypes.bool.isRequired, + text: PropTypes.string.isRequired + }).isRequired).isRequired, + toggleTodo: PropTypes.func.isRequired +} + +export default TodoList diff --git a/docs/examples/todos/src/containers/AddTodo.js b/docs/examples/todos/src/containers/AddTodo.js new file mode 100644 index 0000000..63cfc76 --- /dev/null +++ b/docs/examples/todos/src/containers/AddTodo.js @@ -0,0 +1,27 @@ +import React from 'react' +import { connect } from 'react-redux' +import { addTodo } from '../actions' + +const AddTodo = ({ dispatch }) => { + let input + + return ( +
    +
    { + e.preventDefault() + if (!input.value.trim()) { + return + } + dispatch(addTodo(input.value)) + input.value = '' + }}> + input = node} /> + +
    +
    + ) +} + +export default connect()(AddTodo) diff --git a/docs/examples/todos/src/containers/FilterLink.js b/docs/examples/todos/src/containers/FilterLink.js new file mode 100644 index 0000000..a733d21 --- /dev/null +++ b/docs/examples/todos/src/containers/FilterLink.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { setVisibilityFilter } from '../actions' +import Link from '../components/Link' + +const mapStateToProps = (state, ownProps) => ({ + active: ownProps.filter === state.visibilityFilter +}) + +const mapDispatchToProps = (dispatch, ownProps) => ({ + onClick: () => dispatch(setVisibilityFilter(ownProps.filter)) +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Link) diff --git a/docs/examples/todos/src/containers/VisibleTodoList.js b/docs/examples/todos/src/containers/VisibleTodoList.js new file mode 100644 index 0000000..1c69a9e --- /dev/null +++ b/docs/examples/todos/src/containers/VisibleTodoList.js @@ -0,0 +1,30 @@ +import { connect } from 'react-redux' +import { toggleTodo } from '../actions' +import TodoList from '../components/TodoList' +import { VisibilityFilters } from '../actions' + +const getVisibleTodos = (todos, filter) => { + switch (filter) { + case VisibilityFilters.SHOW_ALL: + return todos + case VisibilityFilters.SHOW_COMPLETED: + return todos.filter(t => t.completed) + case VisibilityFilters.SHOW_ACTIVE: + return todos.filter(t => !t.completed) + default: + throw new Error('Unknown filter: ' + filter) + } +} + +const mapStateToProps = state => ({ + todos: getVisibleTodos(state.todos, state.visibilityFilter) +}) + +const mapDispatchToProps = dispatch => ({ + toggleTodo: id => dispatch(toggleTodo(id)) +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TodoList) diff --git a/docs/examples/todos/src/index.js b/docs/examples/todos/src/index.js new file mode 100644 index 0000000..a82802c --- /dev/null +++ b/docs/examples/todos/src/index.js @@ -0,0 +1,15 @@ +import React from 'react' +import { render } from 'react-dom' +import { createStore } from 'redux' +import { Provider } from 'react-redux' +import App from './components/App' +import rootReducer from './reducers' + +const store = createStore(rootReducer) + +render( + + + , + document.getElementById('root') +) diff --git a/docs/examples/todos/src/reducers/index.js b/docs/examples/todos/src/reducers/index.js new file mode 100644 index 0000000..54b8f0f --- /dev/null +++ b/docs/examples/todos/src/reducers/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux' +import todos from './todos' +import visibilityFilter from './visibilityFilter' + +export default combineReducers({ + todos, + visibilityFilter +}) diff --git a/docs/examples/todos/src/reducers/todos.js b/docs/examples/todos/src/reducers/todos.js new file mode 100644 index 0000000..fc6ed32 --- /dev/null +++ b/docs/examples/todos/src/reducers/todos.js @@ -0,0 +1,23 @@ +const todos = (state = [], action) => { + switch (action.type) { + case 'ADD_TODO': + return [ + ...state, + { + id: action.id, + text: action.text, + completed: false + } + ] + case 'TOGGLE_TODO': + return state.map(todo => + (todo.id === action.id) + ? {...todo, completed: !todo.completed} + : todo + ) + default: + return state + } +} + +export default todos diff --git a/docs/examples/todos/src/reducers/todos.spec.js b/docs/examples/todos/src/reducers/todos.spec.js new file mode 100644 index 0000000..88eca35 --- /dev/null +++ b/docs/examples/todos/src/reducers/todos.spec.js @@ -0,0 +1,111 @@ +import todos from './todos' + +describe('todos reducer', () => { + it('should handle initial state', () => { + expect( + todos(undefined, {}) + ).toEqual([]) + }) + + it('should handle ADD_TODO', () => { + expect( + todos([], { + type: 'ADD_TODO', + text: 'Run the tests', + id: 0 + }) + ).toEqual([ + { + text: 'Run the tests', + completed: false, + id: 0 + } + ]) + + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 0 + } + ], { + type: 'ADD_TODO', + text: 'Use Redux', + id: 1 + }) + ).toEqual([ + { + text: 'Run the tests', + completed: false, + id: 0 + }, { + text: 'Use Redux', + completed: false, + id: 1 + } + ]) + + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 0 + }, { + text: 'Use Redux', + completed: false, + id: 1 + } + ], { + type: 'ADD_TODO', + text: 'Fix the tests', + id: 2 + }) + ).toEqual([ + { + text: 'Run the tests', + completed: false, + id: 0 + }, { + text: 'Use Redux', + completed: false, + id: 1 + }, { + text: 'Fix the tests', + completed: false, + id: 2 + } + ]) + }) + + it('should handle TOGGLE_TODO', () => { + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: 'TOGGLE_TODO', + id: 1 + }) + ).toEqual([ + { + text: 'Run the tests', + completed: true, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + +}) diff --git a/docs/examples/todos/src/reducers/visibilityFilter.js b/docs/examples/todos/src/reducers/visibilityFilter.js new file mode 100644 index 0000000..1500d4f --- /dev/null +++ b/docs/examples/todos/src/reducers/visibilityFilter.js @@ -0,0 +1,12 @@ +import { VisibilityFilters } from '../actions' + +const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => { + switch (action.type) { + case 'SET_VISIBILITY_FILTER': + return action.filter + default: + return state + } +} + +export default visibilityFilter