Skip to content

Commit

Permalink
add example todo-mvc with optimistic update
Browse files Browse the repository at this point in the history
  • Loading branch information
Junwen Qin committed Dec 8, 2015
1 parent 7cf378a commit 2f6c2c9
Show file tree
Hide file tree
Showing 14 changed files with 434 additions and 0 deletions.
8 changes: 8 additions & 0 deletions examples/todo-mvc/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"stage": 2,
"optional": [
"es7.classProperties",
"es7.objectRestSpread",
"runtime"
]
}
2 changes: 2 additions & 0 deletions examples/todo-mvc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
_dist/
48 changes: 48 additions & 0 deletions examples/todo-mvc/actions/TodoActions.js
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
27 changes: 27 additions & 0 deletions examples/todo-mvc/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<header className="header">
<h1>todos</h1>
{
isLoading ?
<h3 style={{textAlign: 'center'}} >
Loading. . .
</h3> :
null
}
<TodoInput
newTodo
placeholder="what needs to be done?"
onSave={onAddTodo}
/>
</header>
)
}
}
export default Header
61 changes: 61 additions & 0 deletions examples/todo-mvc/components/TodoInput.js
Original file line number Diff line number Diff line change
@@ -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 (
<input
type="text"
value={this.state.text}
className={classnames({
'new-todo': newTodo,
'edit': editing
})}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleChange.bind(this)}
onKeyDown={this.handleSubmit.bind(this)}
{...others}
/>
)
}

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
62 changes: 62 additions & 0 deletions examples/todo-mvc/components/TodoItem.js
Original file line number Diff line number Diff line change
@@ -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 ?
(
<TodoInput
editing
text={todo.text}
onSave={(text)=>{
editTodo({id: todo.id, text});
this.setState({ editing: false });
}}
/>
):
(
<div className="view">
<input type="checkbox" className="toggle"/>
<label
onDoubleClick={()=>{this.setState({editing: !editing})}}
>{todo.text}</label>
<button
className="destroy"
onClick={()=>{
deleteTodo(todo.id);
}}
></button>
</div>
);

return (
<li
className={classnames({
completed: todo.completed,
editing: this.state.editing
})}
>
{element}
</li>
);
}
}

export default TodoItem
32 changes: 32 additions & 0 deletions examples/todo-mvc/components/TodoList.js
Original file line number Diff line number Diff line change
@@ -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 (
<ul className="todo-list">
{
todos.map((todo, index)=>(
<TodoItem
key={index}
todo={todo}
{...actions}
/>
))
}
</ul>
)
}
}
export default TodoList
29 changes: 29 additions & 0 deletions examples/todo-mvc/container/App.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className="todoapp">
<Header
isLoading = {isLoading}
onAddTodo={(text)=>{
dispatch(addTodo(text))
}}
></Header>
<section className="main">
<TodoList todos={todos} actions={actions}></TodoList>
</section>
</div>
)
}
}

export default connect(state=>state)(App);
13 changes: 13 additions & 0 deletions examples/todo-mvc/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">

</div>
<script type="text/javascript" src="./_dist/bundle.js"></script>
</body>
</html>
24 changes: 24 additions & 0 deletions examples/todo-mvc/index.jsx
Original file line number Diff line number Diff line change
@@ -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(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById('app')
);
26 changes: 26 additions & 0 deletions examples/todo-mvc/middlewares/loadingMiddleWare.js
Original file line number Diff line number Diff line change
@@ -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;
31 changes: 31 additions & 0 deletions examples/todo-mvc/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading

0 comments on commit 2f6c2c9

Please sign in to comment.