├── .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 |
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 |
--------------------------------------------------------------------------------