├── .babelrc ├── .gitignore ├── README.md ├── dist ├── error.html ├── favicon.ico └── index.html ├── package.json ├── server.js ├── src ├── error.html ├── index.html └── scripts │ ├── actions │ └── index.js │ ├── components │ ├── Footer.js │ ├── Link.js │ ├── Todo.js │ ├── TodoApp.js │ └── TodoList.js │ ├── containers │ ├── AddTodo.js │ ├── FilterLink.js │ └── VisibleTodoList.js │ ├── index.js │ ├── reducers │ ├── index.js │ ├── todos.js │ └── visibilityFilter.js │ └── store │ └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | "react-transform" 7 | ], 8 | "extra": { 9 | "react-transform": { 10 | "transforms": [{ 11 | "transform": "react-transform-hmr", 12 | "imports": ["react"], 13 | "locals": ["module"] 14 | }] 15 | } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore test files. 8 | /spec 9 | webpack_spec.config.js 10 | 11 | # Ignore node modules. 12 | /node_modules 13 | 14 | # Ignore temporary files. 15 | .DS_Store 16 | *.log 17 | /log 18 | /tmp 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Todo 2 | 3 | ##Getting Started with Redux 4 | The code is from following the video series [Getting Started with Redux](https://egghead.io/series/getting-started-with-redux) on egghead.io. 5 | 6 | ##Credits 7 | Dan Abramov is the creator of Redux and the videos mentioned above. 8 | -------------------------------------------------------------------------------- /dist/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Error Page

11 | 12 | 13 | -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badnorseman/todos-react-redux/8eceede7728b093b5e0345070913fd26375a2ef8/dist/favicon.ico -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "0.0.1", 4 | "description": "Todo", 5 | "scripts": { 6 | "build": "./node_modules/webpack/bin/webpack.js", 7 | "pretest": "webpack --config webpack_spec.config.js", 8 | "start": "node server.js", 9 | "test": "./node_modules/jasmine/bin/jasmine.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/urbanvikingr/todo.git" 14 | }, 15 | "dependencies": { 16 | "react": "^0.14.3", 17 | "react-dom": "^0.14.3", 18 | "react-redux": "^4.0.0", 19 | "redux": "^3.0.4" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^5.8.22", 23 | "babel-loader": "^5.3.2", 24 | "babel-plugin-react-transform": "^1.1.1", 25 | "express": "^4.13.3", 26 | "jasmine": "^2.4.1", 27 | "jasmine-ajax": "^3.2.0", 28 | "react-hot-loader": "^1.2.8", 29 | "react-transform-hmr": "^1.0.1", 30 | "webpack": "^1.12.9", 31 | "webpack-dev-middleware": "^1.4.0", 32 | "webpack-hot-middleware": "^2.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var webpackDevMiddleware = require("webpack-dev-middleware"); 3 | var webpackHotMiddleware = require("webpack-hot-middleware"); 4 | var config = require("./webpack.config"); 5 | 6 | var app = new (require("express"))(); 7 | var port = 8080; 8 | 9 | var compiler = webpack(config); 10 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); 11 | app.use(webpackHotMiddleware(compiler)); 12 | 13 | app.get("/", function(req, res) { 14 | res.sendFile(__dirname + "/dist/index.html") 15 | }); 16 | 17 | app.listen(port, function(error) { 18 | if (error) { 19 | console.error(error); 20 | } else { 21 | console.info("Listening on port %s. Open http://localhost:%s/ in browser", port, port); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Error Page

11 | 12 | 13 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/scripts/actions/index.js: -------------------------------------------------------------------------------- 1 | let nextTodoId = 0; 2 | export const addTodo = (text) => { 3 | return { 4 | type: "ADD_TODO", 5 | id: nextTodoId++, 6 | text 7 | }; 8 | }; 9 | 10 | export const setVisibilityFilter = (filter) => { 11 | return { 12 | type: "SET_VISIBILITY_FILTER", 13 | filter 14 | }; 15 | }; 16 | 17 | export const toggleTodo = (id) => { 18 | return { 19 | type: "TOGGLE_TODO", 20 | id 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/scripts/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 | -------------------------------------------------------------------------------- /src/scripts/components/Link.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Link = ({ 4 | active, 5 | children, 6 | onClick 7 | }) => { 8 | if (active) { 9 | return {children}; 10 | } 11 | 12 | return ( 13 | { 15 | e.preventDefault(); 16 | onClick(); 17 | }} 18 | > 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export default Link 25 | -------------------------------------------------------------------------------- /src/scripts/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Todo = ({ 4 | onClick, 5 | completed, 6 | text 7 | }) => ( 8 |
  • 17 | {text} 18 |
  • 19 | ); 20 | 21 | export default Todo 22 | -------------------------------------------------------------------------------- /src/scripts/components/TodoApp.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddTodo from "../containers/AddTodo"; 3 | import Footer from "./Footer"; 4 | import VisibleTodoList from "../containers/VisibleTodoList"; 5 | 6 | const TodoApp = () => ( 7 |
    8 | 9 | 10 |
    12 | ); 13 | 14 | export default TodoApp 15 | -------------------------------------------------------------------------------- /src/scripts/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Todo from "./Todo"; 3 | 4 | const TodoList = ({ 5 | todos, 6 | onTodoClick 7 | }) => ( 8 | 17 | ); 18 | 19 | export default TodoList 20 | -------------------------------------------------------------------------------- /src/scripts/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 | input = node; 12 | }} /> 13 | 19 |
    20 | ); 21 | }; 22 | AddTodo = connect()(AddTodo); 23 | 24 | export default AddTodo 25 | -------------------------------------------------------------------------------- /src/scripts/containers/FilterLink.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { setVisibilityFilter } from "../actions"; 4 | import Link from "../components/Link"; 5 | 6 | const mapStateToProps = ( 7 | state, 8 | ownProps 9 | ) => { 10 | return { 11 | active: 12 | ownProps.filter === 13 | state.visibilityFilter 14 | }; 15 | }; 16 | 17 | const mapDispatchToProps = ( 18 | dispatch, 19 | ownProps 20 | ) => { 21 | return { 22 | onClick: () => { 23 | dispatch( 24 | setVisibilityFilter(ownProps.filter) 25 | ); 26 | } 27 | }; 28 | } 29 | 30 | const FilterLink = connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(Link); 34 | 35 | export default FilterLink 36 | -------------------------------------------------------------------------------- /src/scripts/containers/VisibleTodoList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { toggleTodo } from "../actions"; 4 | import TodoList from "../components/TodoList"; 5 | 6 | const getVisibleTodos = ( 7 | todos, 8 | filter 9 | ) => { 10 | switch (filter) { 11 | case "SHOW_ALL": 12 | return todos; 13 | case "SHOW_COMPLETED": 14 | return todos.filter( 15 | t => t.completed 16 | ); 17 | case "SHOW_ACTIVE": 18 | return todos.filter( 19 | t => !t.completed 20 | ); 21 | } 22 | } 23 | 24 | const mapStateToProps = ( 25 | state 26 | ) => { 27 | return { 28 | todos: getVisibleTodos( 29 | state.todos, 30 | state.visibilityFilter 31 | ) 32 | }; 33 | }; 34 | 35 | const mapDispatchToProps = ( 36 | dispatch 37 | ) => { 38 | return { 39 | onTodoClick: (id) => { 40 | dispatch(toggleTodo(id)); 41 | } 42 | }; 43 | }; 44 | 45 | const VisibleTodoList = connect( 46 | mapStateToProps, 47 | mapDispatchToProps 48 | )(TodoList); 49 | 50 | export default VisibleTodoList 51 | -------------------------------------------------------------------------------- /src/scripts/index.js: -------------------------------------------------------------------------------- 1 | import "babel-core/polyfill"; 2 | import React from "react"; 3 | import { render } from "react-dom"; 4 | import { Provider } from "react-redux"; 5 | import { todoStore } from "./store"; 6 | import TodoApp from "./components/TodoApp"; 7 | 8 | render( 9 | 10 | 11 | , 12 | document.getElementById("app") 13 | ); 14 | -------------------------------------------------------------------------------- /src/scripts/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import { todos } from "./todos"; 3 | import { visibilityFilter } from "./visibilityFilter"; 4 | 5 | export const todoApp = combineReducers({ 6 | todos, 7 | visibilityFilter 8 | }); 9 | -------------------------------------------------------------------------------- /src/scripts/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 | export 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 | -------------------------------------------------------------------------------- /src/scripts/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | export const visibilityFilter = ( 2 | state = "SHOW_ALL", 3 | action 4 | ) => { 5 | switch (action.type) { 6 | case "SET_VISIBILITY_FILTER": 7 | return action.filter; 8 | default: 9 | return state; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/scripts/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "redux"; 2 | import { todoApp } from "../reducers"; 3 | 4 | export const todoStore = createStore(todoApp); 5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | module.exports = { 5 | devtool: "eval-source-map", 6 | entry: [ 7 | "webpack-hot-middleware/client", 8 | "./src/scripts/index.js" 9 | ], 10 | output: { 11 | path: path.join(__dirname, "dist"), 12 | filename: "bundle.js", 13 | publicPath: "/" 14 | }, 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.HotModuleReplacementPlugin(), 18 | new webpack.NoErrorsPlugin() 19 | ], 20 | resolve: { 21 | extensions: ["", ".js"] 22 | }, 23 | module: { 24 | loaders: [{ 25 | test: /\.js$/, 26 | loader: "babel", 27 | exclude: /node_modules/, 28 | include: path.join(__dirname, "/src/scripts") 29 | }] 30 | } 31 | }; 32 | --------------------------------------------------------------------------------