├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── client ├── client.js └── index.html ├── components ├── TextDisplay.js ├── TodoInput.js ├── TodoItem.js └── TodoList.js ├── containers └── App.js ├── package.json ├── redux ├── actions.js ├── reducer.js └── store.js ├── server └── server.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 2, 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | "react-display-name", 7 | "react-transform" 8 | ], 9 | "extra": { 10 | "react-transform": { 11 | "transforms": [{ 12 | "transform": "react-transform-hmr", 13 | "imports": ["react"], 14 | "locals": ["module"] 15 | }] 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | dist 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-redux-todo-demo 2 | 3 | This is a demo app based on the [todomvc example](https://github.com/rackt/redux/tree/master/examples/todomvc) in the [redux source code](https://github.com/rackt/redux). 4 | 5 | ### Running the app 6 | 7 | First, `npm install` all of the dependencies. Then, run `npm start` to spin up a nodemon server at port 3000. 8 | 9 | ### Hot module reloading 10 | 11 | Webpack traverses all of the Javascript files (starting with client/client.js and moving through the `import` and/or `require` statements) and bundles them into one file called `bundle.js`. `bundle.js` is then loaded by a script tag in the `index.html`. The server currently has webpack's hot module reloading middleware set up, meaning that any changes to react/redux code will not trigger a complete re-bundle, but rather will update "inline" and the changes will be immediately reflected in the browser. 12 | -------------------------------------------------------------------------------- /client/client.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import App from '../containers/App'; 5 | 6 | var initialState = { 7 | todos: [{ 8 | id: 0, 9 | completed: false, 10 | text: 'Learn how to use react and redux' 11 | }] 12 | } 13 | 14 | var store = require('../redux/store')(initialState); 15 | 16 | render( 17 | 18 | 19 | , 20 | document.getElementById('app') 21 | ); 22 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React redux example 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/TextDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | var TextDisplay = React.createClass({ 4 | 5 | render: function() { 6 | 7 | return ( 8 |
9 | {this.props.passedDownText} 10 |
11 | ) 12 | } 13 | 14 | }); 15 | 16 | module.exports = TextDisplay; 17 | -------------------------------------------------------------------------------- /components/TodoInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextDisplay from './TextDisplay'; 3 | 4 | var TodoInput = React.createClass({ 5 | 6 | handleSubmit: function (e) { 7 | e.preventDefault(); 8 | this.props.addTodo(this.state.text); 9 | this.setState({ 10 | text: '' 11 | }); 12 | }, 13 | 14 | handleChange: function (e) { 15 | this.setState({ 16 | text: e.target.value 17 | }); 18 | }, 19 | 20 | getInitialState: function () { 21 | return { 22 | text: '' 23 | }; 24 | }, 25 | 26 | render: function() { 27 | 28 | return ( 29 |
30 | 31 |
32 | 38 | 41 |
42 | 43 | 44 | 45 |
46 | ) 47 | } 48 | }); 49 | 50 | module.exports = TodoInput; 51 | -------------------------------------------------------------------------------- /components/TodoItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | var TodoItem = React.createClass({ 4 | 5 | handleCompleted: function() { 6 | this.props.completeTodo(this.props.todo.id); 7 | }, 8 | 9 | handleDelete: function() { 10 | this.props.deleteTodo(this.props.todo.id); 11 | }, 12 | 13 | render: function() { 14 | 15 | var textStyle = this.renderTextStyle(); 16 | 17 | return ( 18 | 27 | ) 28 | }, 29 | 30 | renderTextStyle: function () { 31 | return { 32 | color: this.props.todo.completed ? 'lightgrey' : 'black', 33 | textDecoration: this.props.todo.completed ? 'line-through' : 'none' 34 | }; 35 | } 36 | 37 | }); 38 | 39 | module.exports = TodoItem; 40 | -------------------------------------------------------------------------------- /components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TodoItem from './TodoItem' 3 | 4 | var TodoList = React.createClass({ 5 | 6 | render: function() { 7 | 8 | return ( 9 |
10 | 11 | { 12 | this.props.todos.map(function (todo) { 13 | return ( 14 | 19 | ) 20 | }.bind(this)) 21 | } 22 | 23 |
24 | ) 25 | } 26 | }); 27 | 28 | module.exports = TodoList; 29 | -------------------------------------------------------------------------------- /containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { connect } from 'react-redux' 4 | import * as actions from '../redux/actions' 5 | import TodoInput from '../components/TodoInput' 6 | import TodoList from '../components/TodoList' 7 | 8 | var App = React.createClass({ 9 | 10 | render: function() { 11 | return ( 12 |
13 | 14 | 15 |
16 | ) 17 | } 18 | }); 19 | 20 | var mapStateToProps = function (state) { 21 | return state; 22 | }; 23 | 24 | var mapDispatchToProps = function (dispatch) { 25 | return { 26 | actions: bindActionCreators(actions, dispatch) 27 | }; 28 | } 29 | 30 | module.exports = connect(mapStateToProps, mapDispatchToProps)(App); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-todo-demo", 3 | "version": "1.0.0", 4 | "description": "talk and demo of reactjs and redux", 5 | "author": "Kurt Weiberth", 6 | "scripts": { 7 | "start": "nodemon server/server.js --ignore components --ignore containers" 8 | }, 9 | "dependencies": { 10 | "babel-core": "^5.8.3", 11 | "babel-loader": "^5.3.2", 12 | "babel-plugin-react-display-name": "^2.0.0", 13 | "babel-plugin-react-transform": "^1.1.0", 14 | "babel-runtime": "^5.8.20", 15 | "express": "^4.13.3", 16 | "react": "^0.14.3", 17 | "react-dom": "^0.14.3", 18 | "react-redux": "^4.0.0", 19 | "react-transform-hmr": "^1.0.1", 20 | "redux": "^3.0.4", 21 | "redux-logger": "^2.0.4", 22 | "redux-thunk": "^1.0.0", 23 | "webpack": "^1.12.9", 24 | "webpack-dev-middleware": "^1.4.0", 25 | "webpack-hot-middleware": "^2.5.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /redux/actions.js: -------------------------------------------------------------------------------- 1 | var actions = { 2 | 3 | addTodo: function (text) { 4 | return { 5 | type: 'ADD_TODO', 6 | text: text 7 | }; 8 | }, 9 | 10 | deleteTodo: function (id) { 11 | return { 12 | type: 'DELETE_TODO', 13 | id: id 14 | }; 15 | }, 16 | 17 | completeTodo: function (id) { 18 | return { 19 | type: 'COMPLETE_TODO', 20 | id: id 21 | }; 22 | } 23 | 24 | }; 25 | 26 | module.exports = actions; 27 | -------------------------------------------------------------------------------- /redux/reducer.js: -------------------------------------------------------------------------------- 1 | var getId = function (state) { 2 | return state.todos.reduce(function (maxId, todo) { 3 | return Math.max(todo.id, maxId) 4 | }, -1) + 1; 5 | }; 6 | 7 | var reducer = function (state, action) { 8 | 9 | switch (action.type) { 10 | 11 | case 'ADD_TODO': 12 | return Object.assign({}, state, { 13 | todos: [{ 14 | id: getId(state), 15 | completed: false, 16 | text: action.text 17 | }, ...state.todos] 18 | }); 19 | 20 | case 'DELETE_TODO': 21 | 22 | return Object.assign({}, state, { 23 | todos: state.todos.filter(function (todo) { 24 | return todo.id !== action.id 25 | }) 26 | }); 27 | 28 | case 'COMPLETE_TODO': 29 | console.log(action.id); 30 | return Object.assign({}, state, { 31 | todos: state.todos.map(function (todo) { 32 | return todo.id === action.id ? 33 | Object.assign({}, todo, {completed: !todo.completed}) : 34 | todo 35 | }) 36 | }); 37 | 38 | default: 39 | return state; 40 | } 41 | }; 42 | 43 | module.exports = reducer; 44 | -------------------------------------------------------------------------------- /redux/store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore } from 'redux'; 2 | import reducer from './reducer'; 3 | import logger from 'redux-logger'; 4 | import thunk from 'redux-thunk'; 5 | 6 | var finalCreateStore = compose( 7 | applyMiddleware(thunk, logger()) 8 | )(createStore); 9 | 10 | var configureStore = function(initialState) { 11 | initialState = initialState || {todos: []} 12 | return finalCreateStore(reducer, initialState); 13 | }; 14 | 15 | module.exports = configureStore; 16 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var config = require('../webpack.config') 2 | var express = require('express'); 3 | var path = require('path'); 4 | var webpack = require('webpack') 5 | var webpackDevMiddleware = require('webpack-dev-middleware') 6 | var webpackHotMiddleware = require('webpack-hot-middleware') 7 | 8 | var app = express(); 9 | var port = 3000; 10 | 11 | var compiler = webpack(config); 12 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); 13 | app.use(webpackHotMiddleware(compiler)); 14 | 15 | app.use(express.static('./dist')); 16 | 17 | app.get("/", function(req, res) { 18 | res.sendFile(path.resolve('client/index.html')); 19 | }); 20 | 21 | app.listen(port, function(error) { 22 | if (error) { 23 | console.error(error) 24 | } else { 25 | console.log("Express server listening on port", port); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /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/client.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 | module: { 21 | loaders: [{ 22 | test: /\.js$/, 23 | loaders: ['babel-loader'], 24 | exclude: /node_modules/ 25 | }] 26 | } 27 | } 28 | --------------------------------------------------------------------------------