From 2f6c2c9af11ae99b6d28f5767f9313d800df6bda Mon Sep 17 00:00:00 2001 From: Junwen Qin Date: Tue, 8 Dec 2015 10:37:37 +0800 Subject: [PATCH] add example todo-mvc with optimistic update --- examples/todo-mvc/.babelrc | 8 +++ examples/todo-mvc/.gitignore | 2 + examples/todo-mvc/actions/TodoActions.js | 48 ++++++++++++++ examples/todo-mvc/components/Header.jsx | 27 ++++++++ examples/todo-mvc/components/TodoInput.js | 61 ++++++++++++++++++ examples/todo-mvc/components/TodoItem.js | 62 +++++++++++++++++++ examples/todo-mvc/components/TodoList.js | 32 ++++++++++ examples/todo-mvc/container/App.js | 29 +++++++++ examples/todo-mvc/index.html | 13 ++++ examples/todo-mvc/index.jsx | 24 +++++++ .../todo-mvc/middlewares/loadingMiddleWare.js | 26 ++++++++ examples/todo-mvc/package.json | 31 ++++++++++ examples/todo-mvc/reducers/index.js | 37 +++++++++++ examples/todo-mvc/webpack.config.js | 34 ++++++++++ 14 files changed, 434 insertions(+) create mode 100644 examples/todo-mvc/.babelrc create mode 100644 examples/todo-mvc/.gitignore create mode 100644 examples/todo-mvc/actions/TodoActions.js create mode 100644 examples/todo-mvc/components/Header.jsx create mode 100644 examples/todo-mvc/components/TodoInput.js create mode 100644 examples/todo-mvc/components/TodoItem.js create mode 100644 examples/todo-mvc/components/TodoList.js create mode 100644 examples/todo-mvc/container/App.js create mode 100644 examples/todo-mvc/index.html create mode 100644 examples/todo-mvc/index.jsx create mode 100644 examples/todo-mvc/middlewares/loadingMiddleWare.js create mode 100644 examples/todo-mvc/package.json create mode 100644 examples/todo-mvc/reducers/index.js create mode 100644 examples/todo-mvc/webpack.config.js diff --git a/examples/todo-mvc/.babelrc b/examples/todo-mvc/.babelrc new file mode 100644 index 0000000..213ba0c --- /dev/null +++ b/examples/todo-mvc/.babelrc @@ -0,0 +1,8 @@ +{ + "stage": 2, + "optional": [ + "es7.classProperties", + "es7.objectRestSpread", + "runtime" + ] +} \ No newline at end of file diff --git a/examples/todo-mvc/.gitignore b/examples/todo-mvc/.gitignore new file mode 100644 index 0000000..4291482 --- /dev/null +++ b/examples/todo-mvc/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +_dist/ \ No newline at end of file diff --git a/examples/todo-mvc/actions/TodoActions.js b/examples/todo-mvc/actions/TodoActions.js new file mode 100644 index 0000000..cb153cb --- /dev/null +++ b/examples/todo-mvc/actions/TodoActions.js @@ -0,0 +1,48 @@ +/** + * Created by jwqin on 11/16/15. + */ +import _ from 'lodash' +import {createPromiseThunk} from 'redux-promise-thunk' + +function apiMock(resp, err = new Error('Oops')) { + return new Promise(function(resolve, reject){ + setTimeout(()=>{ + Math.random() < 0.7 ? + resolve(resp): + reject(err); + }, 300) + }); +} + +export default { + addTodo: createPromiseThunk('ADD_TODO', function(text) { + return apiMock({ + id: _.uniqueId('TODO_'), + text + }) + }), + //Use thunk for optimistic update + //since editTodoAction returned by createPromiseThunk is a thunk, the editTodo comes to be a composed thunk. + editTodo: function (todo) { + return function (dispatch, getState) { + const oldTodo = _.find(getState().todos, item=>item.id === todo.id); + + const editTodoAction = createPromiseThunk('EDIT_TODO', function (todo) { + const error = new Error('shit'); + error.old = oldTodo; + + return apiMock(todo, error); + }); + + dispatch(editTodoAction(todo)); + } + }, + deleteTodo(id){ + return { + type: 'DELETE_TODO', + payload: { + id + } + } + } +} \ No newline at end of file diff --git a/examples/todo-mvc/components/Header.jsx b/examples/todo-mvc/components/Header.jsx new file mode 100644 index 0000000..f1b5774 --- /dev/null +++ b/examples/todo-mvc/components/Header.jsx @@ -0,0 +1,27 @@ +import React , {Component} from 'react' +import TodoInput from './TodoInput.js' + +class Header extends Component{ + render(){ + const {isLoading, onAddTodo} = this.props; + + return ( +
+

todos

+ { + isLoading ? +

+ Loading. . . +

: + null + } + +
+ ) + } +} +export default Header \ No newline at end of file diff --git a/examples/todo-mvc/components/TodoInput.js b/examples/todo-mvc/components/TodoInput.js new file mode 100644 index 0000000..e583a9f --- /dev/null +++ b/examples/todo-mvc/components/TodoInput.js @@ -0,0 +1,61 @@ +/** + * Created by jwqin on 11/15/15. + */ +import React, {Component, PropTypes} from 'react' +import classnames from 'classnames' + +class TodoInput extends Component{ + static propTypes: { + newTodo: PropTypes.bool, + editing: PropTypes.bool, + onSave: PropTypes.func, + text: PropTypes.string + }; + + constructor(props){ + super(props); + + this.state = { + text: props.text || '' + } + } + + render(){ + const {newTodo, editing, ...others} = this.props; + return ( + + ) + } + + handleSubmit(e) { + const text = e.target.value.trim(); + if (e.which === 13) { + this.props.onSave(text); + if (this.props.newTodo) { + this.setState({ text: '' }); + } + } + } + + handleChange(e) { + this.setState({ text: e.target.value }); + } + + handleBlur(e) { + if (!this.props.newTodo) { + this.props.onSave(e.target.value); + } + } +} +export default TodoInput diff --git a/examples/todo-mvc/components/TodoItem.js b/examples/todo-mvc/components/TodoItem.js new file mode 100644 index 0000000..b40e2a2 --- /dev/null +++ b/examples/todo-mvc/components/TodoItem.js @@ -0,0 +1,62 @@ +/** + * Created by jwqin on 11/15/15. + */ +import React, {Component, PropTypes} from 'react' +import classnames from 'classnames' +import TodoInput from './TodoInput.js' + +class TodoItem extends Component { + static propTypes: { + todo: PropTypes.object + }; + constructor(props){ + super(props); + + this.state = { + editing: false + } + } + render(){ + const {todo, editTodo, deleteTodo} = this.props; + const {editing} = this.state; + + const element = editing ? + ( + { + editTodo({id: todo.id, text}); + this.setState({ editing: false }); + }} + /> + ): + ( +
+ + + +
+ ); + + return ( +
  • + {element} +
  • + ); + } +} + +export default TodoItem \ No newline at end of file diff --git a/examples/todo-mvc/components/TodoList.js b/examples/todo-mvc/components/TodoList.js new file mode 100644 index 0000000..82eb6e6 --- /dev/null +++ b/examples/todo-mvc/components/TodoList.js @@ -0,0 +1,32 @@ +/** + * Created by jwqin on 11/15/15. + */ +import React , {Component, PropTypes} from 'react' +import TodoItem from './TodoItem.js' + +class TodoList extends Component{ + static propTypes = { + todos: PropTypes.array + } + + static test123= {} + + render(){ + const {todos, actions} = this.props; + + return ( + + ) + } +} +export default TodoList \ No newline at end of file diff --git a/examples/todo-mvc/container/App.js b/examples/todo-mvc/container/App.js new file mode 100644 index 0000000..1561f9a --- /dev/null +++ b/examples/todo-mvc/container/App.js @@ -0,0 +1,29 @@ +import React, {Component} from 'react' +import {bindActionCreators} from 'redux' +import {connect} from 'react-redux' +import 'todomvc-app-css/index.css' +import TodoActions, {addTodo } from '../actions/TodoActions.js' +import Header from '../components/Header.jsx' +import TodoList from '../components/TodoList.js' + +class App extends Component { + render(){ + const {dispatch, todos, isLoading} = this.props; + const actions = bindActionCreators(TodoActions, dispatch) + return ( +
    +
    { + dispatch(addTodo(text)) + }} + >
    +
    + +
    +
    + ) + } +} + +export default connect(state=>state)(App); diff --git a/examples/todo-mvc/index.html b/examples/todo-mvc/index.html new file mode 100644 index 0000000..f2ad304 --- /dev/null +++ b/examples/todo-mvc/index.html @@ -0,0 +1,13 @@ + + + + + + + +
    + +
    + + + \ No newline at end of file diff --git a/examples/todo-mvc/index.jsx b/examples/todo-mvc/index.jsx new file mode 100644 index 0000000..57708cf --- /dev/null +++ b/examples/todo-mvc/index.jsx @@ -0,0 +1,24 @@ +import 'babel-core/polyfill'; +import React, {Component} from 'react' +import {render} from 'react-dom' +import {createStore, applyMiddleware} from 'redux' +import App from './container/App' +import {Provider} from 'react-redux' +import reducers from './reducers' +import createLogger from 'redux-logger' +import thunk from 'redux-thunk' +import loadingMiddleWare from './middlewares/loadingMiddleWare' + +const store = applyMiddleware( + thunk, + loadingMiddleWare, + createLogger() +)(createStore)(reducers, {todos: [{text: 'adfsadsf', id: 0}]}); + +render( + + + + , + document.getElementById('app') +); \ No newline at end of file diff --git a/examples/todo-mvc/middlewares/loadingMiddleWare.js b/examples/todo-mvc/middlewares/loadingMiddleWare.js new file mode 100644 index 0000000..7e1aceb --- /dev/null +++ b/examples/todo-mvc/middlewares/loadingMiddleWare.js @@ -0,0 +1,26 @@ +import _ from 'lodash' +import {steps} from 'redux-promise-thunk' + +function loadingMiddleWare({dispatch}) { + return next => action => { + const asyncStep = _.get(action, 'meta.asyncStep'); + if (asyncStep === steps.START) { + dispatch({ + type: 'ASYNC_STARTED', + payload: { + action + } + }) + } else if (asyncStep === steps.COMPLETED || asyncStep === steps.FAILED) { + dispatch({ + type: 'ASYNC_ENDED', + payload: { + action + } + }) + } + next(action); + } +} + +export default loadingMiddleWare; \ No newline at end of file diff --git a/examples/todo-mvc/package.json b/examples/todo-mvc/package.json new file mode 100644 index 0000000..33a7b56 --- /dev/null +++ b/examples/todo-mvc/package.json @@ -0,0 +1,31 @@ +{ + "name": "redux_example", + "version": "1.0.0", + "description": "", + "scripts": { + "start" : "webpack --watch" + }, + "author": "kpaxqin", + "license": "MIT", + "dependencies": { + "history": "^1.13.1", + "lodash": "^3.10.1", + "react": "^0.14.2", + "react-dom": "^0.14.2", + "react-redux": "^4.0.0", + "redux": "^3.0.4", + "redux-logger": "^2.0.4", + "redux-thunk": "^1.0.0", + "redux-promise-thunk": "^0.1.3" + }, + "devDependencies": { + "babel-loader": "^5.4.0", + "babel-runtime": "^6.1.18", + "classnames": "^2.2.0", + "config": "^1.16.0", + "raw-loader": "^0.5.1", + "style-loader": "^0.12.3", + "todomvc-app-css": "^2.0.3", + "webpack": "^1.12.6" + } +} diff --git a/examples/todo-mvc/reducers/index.js b/examples/todo-mvc/reducers/index.js new file mode 100644 index 0000000..db9e2fa --- /dev/null +++ b/examples/todo-mvc/reducers/index.js @@ -0,0 +1,37 @@ +/** + * Created by jwqin on 11/15/15. + */ +import {combineReducers} from 'redux' +import _ from 'lodash' + +export default combineReducers({ + todos: function (state = [], action){ + const {type, payload, error} = action; + switch (type){ + case 'ADD_TODO_COMPLETED': + return [ + payload, + ...state + ]; + case 'EDIT_TODO_START': + case 'EDIT_TODO_COMPLETED': + return _.map(state, (item)=> item.id === payload.id ? payload: item); + case 'EDIT_TODO_FAILED': + return _.map(state, (item)=> item.id === payload.old.id ? payload.old : item); + case 'DELETE_TODO': + return _.filter(state, (item)=>item.id !== payload.id); + } + return state; + }, + isLoading: function (state = false, action) { + const {type} = action; + + switch (type){ + case 'ASYNC_STARTED': + return true; + case 'ASYNC_ENDED': + return false; + } + return state; + } +}) \ No newline at end of file diff --git a/examples/todo-mvc/webpack.config.js b/examples/todo-mvc/webpack.config.js new file mode 100644 index 0000000..3c0e1f1 --- /dev/null +++ b/examples/todo-mvc/webpack.config.js @@ -0,0 +1,34 @@ +/** + * Created by jwqin on 11/15/15. + */ +var path = require('path'); +var webpack = require('webpack'); + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +module.exports = { + devtool: 'cheap-module-eval-source-map', + entry: [ + './index.jsx' + ], + output: { + path: path.join(__dirname, '_dist'), + filename: 'bundle.js', + publicPath: '/static/' + }, + module: { + loaders: [{ + test: /\.jsx?$/, + loaders: ['babel'], + exclude: /node_modules/, + include: __dirname + }, { + test: /\.css?$/, + loaders: ['style', 'raw'], + include: __dirname + }] + }, + resolve: { + extensions: ['', '.jsx', '.js'] + } +};