├── 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 |
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 |
34 | {todos.map(todo =>
35 | onTodoClick(todo.id)}
39 | />
40 | )}
41 |
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 |
--------------------------------------------------------------------------------