├── app ├── js │ ├── app.js │ ├── actions │ │ └── index.js │ ├── components │ │ ├── AppRoot.jsx │ │ ├── AddTodo.jsx │ │ ├── Footer.jsx │ │ └── TodoList.jsx │ └── store │ │ └── index.js └── index.html ├── README.md ├── webpack.config.js ├── package.json └── .gitignore /app/js/app.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | 3 | import AppRoot from './components/AppRoot.jsx'; 4 | 5 | ReactDOM.render( 6 | AppRoot, 7 | document.getElementById('app-root') 8 | ); 9 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | todo 6 | 7 | 8 |

todo

9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/js/actions/index.js: -------------------------------------------------------------------------------- 1 | let nextTodoId = 0; 2 | 3 | export const addTodo = (text) => { 4 | return { 5 | type: 'ADD_TODO', 6 | id: nextTodoId++, 7 | text 8 | }; 9 | }; 10 | 11 | export const toggleTodo = (id) => { 12 | return { 13 | type: 'TOGGLE_TODO', 14 | id 15 | }; 16 | }; 17 | 18 | export const setVisibilityFilter = (filter) => { 19 | return { 20 | type: 'SET_VISIBILITY_FILTER', 21 | filter 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /app/js/components/AppRoot.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | 4 | import AddTodo from './AddTodo.jsx'; 5 | import TodoList from './TodoList.jsx'; 6 | import Footer from './Footer.jsx'; 7 | import store from '../store'; 8 | 9 | 10 | const TodoApp = () => ( 11 |
12 | 13 | 14 |
16 | ); 17 | 18 | export default ( 19 | 20 | 21 | 22 | ) -------------------------------------------------------------------------------- /app/js/components/AddTodo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { addTodo } from '../actions'; 5 | 6 | let AddTodo = ({ dispatch }) => { 7 | let input; 8 | 9 | return ( 10 |
11 | { 12 | input = node; 13 | }} /> 14 | 20 |
21 | ); 22 | }; 23 | export default connect()(AddTodo); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Todo App using React Redux Webpack 2 | 3 | Yet another Todo app using React Redux and Webpack. 4 | 5 | The content is based on the example in the 6 | [great Redux tutorial by Dan Abramov](https://egghead.io/series/getting-started-with-redux). 7 | 8 | The aim here was to keep it *really* simple to get started with these technologies. 9 | 10 | ## Deps 11 | 12 | npm install 13 | 14 | This repository is meant to be used with npm 3. For version 2 also install: 15 | 16 | npm install --save-dev babel-plugin-transform-object-rest-spread 17 | npm install --save-dev babel-plugin-transform-class-properties 18 | 19 | ## Build 20 | 21 | node_modules/.bin/webpack 22 | 23 | ## Dev 24 | 25 | node_modules/.bin/webpack-dev-server --hot --inline 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | javascript: "./app/js/app.js", 4 | html: "./app/index.html" 5 | }, 6 | output: { 7 | path: __dirname + "/dist", 8 | filename: "./js/app.js" 9 | }, 10 | module: { 11 | loaders: [ 12 | { 13 | test: /\.html$/, 14 | loader: "file?name=[name].[ext]" 15 | }, 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | loaders: ["react-hot", 'babel?'+JSON.stringify( 20 | { 21 | presets: ['react', 'es2015'], 22 | "plugins": [ 23 | "syntax-class-properties", 24 | "syntax-decorators", 25 | "syntax-object-rest-spread", 26 | 27 | "transform-class-properties", 28 | "transform-object-rest-spread" 29 | ] 30 | } 31 | )] 32 | } 33 | ] 34 | } 35 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Todo", 3 | "version": "0.0.0", 4 | "description": "Todo app using React, Redux and Webpack. Based on the example from Dan Abramov's excellent tutorial (https://egghead.io/series/getting-started-with-redux)", 5 | "scripts": { 6 | "build": "webpack", 7 | "start": "webpack-dev-server --hot --inline" 8 | }, 9 | "keywords": [ 10 | "todo" 11 | ], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "babel-core": "^6.3.26", 16 | "babel-loader": "^6.2.0", 17 | "babel-plugin-react-transform": "^2.0.0", 18 | "babel-plugin-syntax-class-properties": "^6.3.13", 19 | "babel-plugin-syntax-decorators": "^6.3.13", 20 | "babel-plugin-syntax-object-rest-spread": "^6.3.13", 21 | "babel-preset-es2015": "^6.3.13", 22 | "babel-preset-react": "^6.3.13", 23 | "babel-preset-stage-0": "^6.3.13", 24 | "file-loader": "^0.8.5", 25 | "react-hot-loader": "^1.3.0", 26 | "webpack": "^1.12.9", 27 | "webpack-dev-server": "^1.14.0" 28 | }, 29 | "dependencies": { 30 | "react": "^0.14.5", 31 | "react-dom": "^0.14.5", 32 | "react-redux": "^4.0.6", 33 | "redux": "^3.0.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/js/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers } from 'redux'; 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 visibilityFilter = ( 42 | state = 'SHOW_ALL', 43 | action 44 | ) => { 45 | switch (action.type) { 46 | case 'SET_VISIBILITY_FILTER': 47 | return action.filter; 48 | default: 49 | return state; 50 | } 51 | }; 52 | 53 | const todoApp = combineReducers({ 54 | todos, 55 | visibilityFilter 56 | }); 57 | 58 | export default createStore(todoApp) -------------------------------------------------------------------------------- /app/js/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { setVisibilityFilter } from '../actions'; 5 | 6 | const Link = ({ 7 | active, 8 | children, 9 | onClick 10 | }) => { 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 | const mapStateProps = ( 28 | state, 29 | ownProps 30 | ) => { 31 | return { 32 | active: 33 | ownProps.filter === 34 | state.visibilityFilter 35 | }; 36 | }; 37 | const mapDispatchProps = ( 38 | dispatch, 39 | ownProps 40 | ) => { 41 | return { 42 | onClick: () => { 43 | dispatch( 44 | setVisibilityFilter(ownProps.filter) 45 | ); 46 | } 47 | }; 48 | }; 49 | 50 | const FilterLink = connect( 51 | mapStateProps, 52 | mapDispatchProps 53 | )(Link); 54 | 55 | export default () => ( 56 |

57 | Show: 58 | {' '} 59 | 60 | All 61 | 62 | {', '} 63 | 64 | Active 65 | 66 | {', '} 67 | 68 | Completed 69 | 70 |

71 | ); -------------------------------------------------------------------------------- /app/js/components/TodoList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { toggleTodo } from '../actions'; 5 | 6 | const Todo = ({ 7 | onClick, 8 | completed, 9 | text 10 | }) => ( 11 |
  • 25 | {text} 26 |
  • 27 | ); 28 | 29 | const TodoList = ({ 30 | todos, 31 | onTodoClick 32 | }) => ( 33 | 42 | ); 43 | 44 | const getVisibleTodos = ( 45 | todos, 46 | filter 47 | ) => { 48 | switch (filter) { 49 | case 'SHOW_ALL': 50 | return todos; 51 | case 'SHOW_COMPLETED': 52 | return todos.filter( 53 | t => t.completed 54 | ); 55 | case 'SHOW_ACTIVE': 56 | return todos.filter( 57 | t => !t.completed 58 | ); 59 | } 60 | } 61 | 62 | const mapStateToProps = ( 63 | state 64 | ) => { 65 | return { 66 | todos: getVisibleTodos( 67 | state.todos, 68 | state.visibilityFilter 69 | ) 70 | }; 71 | }; 72 | const mapDispatchToProps = ( 73 | dispatch 74 | ) => { 75 | return { 76 | onTodoClick: (id) => { 77 | dispatch(toggleTodo(id)); 78 | } 79 | }; 80 | }; 81 | export default connect( 82 | mapStateToProps, 83 | mapDispatchToProps 84 | )(TodoList); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | /*.example.* 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 31 | node_modules 32 | ### JetBrains template 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 34 | 35 | *.iml 36 | 37 | ## Directory-based project format: 38 | .idea/ 39 | # if you remove the above rule, at least ignore the following: 40 | 41 | # User-specific stuff: 42 | # .idea/workspace.xml 43 | # .idea/tasks.xml 44 | # .idea/dictionaries 45 | 46 | # Sensitive or high-churn files: 47 | # .idea/dataSources.ids 48 | # .idea/dataSources.xml 49 | # .idea/sqlDataSources.xml 50 | # .idea/dynamic.xml 51 | # .idea/uiDesigner.xml 52 | 53 | # Gradle: 54 | # .idea/gradle.xml 55 | # .idea/libraries 56 | 57 | # Mongo Explorer plugin: 58 | # .idea/mongoSettings.xml 59 | 60 | ## File-based project format: 61 | *.ipr 62 | *.iws 63 | 64 | ## Plugin-specific files: 65 | 66 | # IntelliJ 67 | /out/ 68 | 69 | # mpeltonen/sbt-idea plugin 70 | .idea_modules/ 71 | 72 | # JIRA plugin 73 | atlassian-ide-plugin.xml 74 | 75 | # Crashlytics plugin (for Android Studio and IntelliJ) 76 | com_crashlytics_export_strings.xml 77 | crashlytics.properties 78 | crashlytics-build.properties 79 | 80 | --------------------------------------------------------------------------------