Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save linuxenko/0117b5b8cab3fc024af3 to your computer and use it in GitHub Desktop.
Save linuxenko/0117b5b8cab3fc024af3 to your computer and use it in GitHub Desktop.
React + Redux + ReactRedux TodoList
class Actions {
static createTodo(title = 'Empty todo', completed = false) {
return {
type : 'ADD_TODO',
payload : {
id : null,
title : title,
completed : completed
}
};
}
static toggleTodo(id) {
return {
type : 'TOGGLE_TODO',
payload : id
}
}
static editTodo(id, title) {
return {
type: 'EDIT_TODO',
payload : {
id : id,
title : title
}
}
}
static removeTodo(id) {
return {
type : 'REMOVE_TODO',
payload : id
}
}
static toggleAll(checked) {
return {
type : 'TOGGLE_ALL',
payload: checked
}
}
static reducer(state, action) {
switch(action.type) {
case 'ADD_TODO' :
let item = Object.assign({}, action.payload);
item.id = Array.isArray(state) ?
state.length : typeof state === 'object' ? 1 : 0;
return state ? [...state,...item] : item;
case 'TOGGLE_TODO' :
return state.map((s) => {
return s.id === action.payload ?
Object.assign({}, s, {'completed' : !s.completed}) : s;
});
case 'REMOVE_TODO' :
return state.filter(s => s.id !== action.payload);
case 'EDIT_TODO' :
return state.map(s => s.id === action.payload.id ?
Object.assign({}, s, action.payload) : s );
case 'TOGGLE_ALL' :
return state.map(s => Object.assign(s, {completed : action.payload}));
}
}
}
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = { filter : 1 };
}
showEditor(e) {
e.target.parentNode.parentNode.classList.add('editing');
}
removeEditing() {
[].slice.call(document.querySelectorAll('.editing'))
.map((e) => e.classList.remove('editing'));
}
editTodo(id, e) {
if (e.key === 'Enter' && e.target.value.length > 0) {
this.props.dispatch(Actions.editTodo(id, e.target.value));
this.removeEditing();
}
}
createTodo(e) {
if (e.key === 'Enter' && e.target.value.length > 0) {
this.props.dispatch(Actions.createTodo(e.target.value, false));
e.target.value = '';
}
}
onFilter(filter, e) {
[].slice.call(document.querySelectorAll('.filters .selected'))
.map(s => s.classList.remove('selected'));
e.target.classList.add('selected');
this.setState({filter : filter});
}
onClearCompleted() {
this.props.todos.map(todo => {
if (todo.completed === true) {
this.props.dispatch(Actions.removeTodo(todo.id));
}
});
}
toggleAll(e) {
this.props.dispatch(Actions.toggleAll(e.target.checked));
}
render() {
let itemsLeft = this.props.todos.reduce((p,c) => c.completed ? p : p +1, 0);
let items = this.props.todos
.filter(todo => todo.completed !== this.state.filter)
.sort((a,b) => b.id - a.id)
.map((todo) => {
return (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<div className="view" onClick={this.removeEditing}>
<input
onChange={this.props.dispatch.bind(this,Actions.toggleTodo(todo.id))}
className="toggle" type="checkbox"
checked={todo.completed} />
<label onDoubleClick={this.showEditor}>
{todo.title}
</label>
<button
onClick={this.props.dispatch.bind(this, Actions.removeTodo(todo.id))}
className="destroy"></button>
</div>
<input className="edit"
onKeyDown={this.editTodo.bind(this, todo.id)}
defaultValue={todo.title} />
</li>
);
});
return (
<div>
<section className="todoapp">
<header className="header">
<h1>react+redux</h1>
<input className="new-todo" onKeyDown={this.createTodo.bind(this)}
placeholder="What needs to be done?" autofocus />
</header>
<section className="main">
<input className="toggle-all" onChange={this.toggleAll.bind(this)}
type="checkbox" />
<label htmlFor="toggle-all">Mark all as complete</label>
<ul className="todo-list">
{items}
</ul>
</section>
<footer className="footer">
<span className="todo-count">
<strong>{itemsLeft}</strong> item left</span>
<ul className="filters">
<li>
<a onClick={this.onFilter.bind(this, 1)}
className="selected" href="#">All</a>
</li>
<li>
<a onClick={this.onFilter.bind(this, true)} href="#">Active</a>
</li>
<li>
<a onClick={this.onFilter.bind(this, false)} href="#">Completed</a>
</li>
</ul>
<button onClick={this.onClearCompleted.bind(this)}
className="clear-completed">Clear completed</button>
</footer>
</section>
<footer className="info">
<p>Double-click to edit a todo</p>
</footer>
</div>
)
}
}
let ReduxedApp = ReactRedux.connect((state) => { return {todos : state} })(TodoApp);
let Provider = ReactRedux.Provider;
let store = Redux.createStore(Actions.reducer);
store.dispatch(Actions.createTodo('Handle redux actions', true));
store.dispatch(Actions.createTodo('Implement filtering action', true));
store.dispatch(Actions.createTodo('Setup boilerplate', false));
store.dispatch(Actions.createTodo('Understand the fact i know redux', false));
store.dispatch(Actions.toggleTodo(2));
React.render(
<Provider store={store}>
<ReduxedApp />
</Provider>
, document.body);
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/redux/3.1.6/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.1/react-redux.js"></script>
<link href="https://cdn.rawgit.com/tastejs/todomvc-app-css/master/index.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment