<div id="app"></div>
.todos-list-group .list-group-item.disabled
  text-decoration: line-through
  font-style: italic
  background-color: #efefef
  
.welcome-text
  height: 0
  overflow: hidden
  transition: height .25s ease-in-out
  text-align: center
  color: #444

.welcome-text--visible
  height: 50px

//
// -- State management
//

// Settings reducer
const settingsInitialState = { name: 'redux-todo-app' }
const settingsReducer = (state = settingsInitialState, action) => state

// Todos reducer
const todosInitialState = []
const todosReducer = (state = todosInitialState, action) => {
  switch (action.type) {
    case 'add-todo': return [ ...state, action.todo ]
    case 'toggle-todo': return [ ...state.map(todo => {
      if (todo.id === action.todoId) {
        return {
          ...todo,
          status: !todo.status
        }
      }
      return todo
    }) ]
    case 'clear-done-todos': return [ ...state.filter(todo => !todo.status) ]
    default: return state
  }
}

const reducers = Redux.combineReducers({
  settings: settingsReducer,
  todos: todosReducer,
})

const appInitialState = {
  settings: { name: 'TodosDemo' },
  todos: [
 //   { id: 1, status: false, content: 'Buy milk' },
 //   { id: 2, status: true, content: 'Clean home' },
  ],
}

// compose the store with the debug tool if available
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || Redux.compose
const enhancers = composeEnhancers(Redux.applyMiddleware(ReduxThunk.default))
const store = Redux.createStore(reducers, appInitialState, enhancers)


//
// -- Service Layer (thunks)
//

const addTodo = content => dispatch => {
  dispatch({
    type: 'add-todo',
    todo: {
      id: Date.now(),
      status: false,
      content,
    }
  })
}


//
// -- Dumb Components
//

const getTodoClass = status => {
  const classes = [ 'list-group-item' ]
  if (status) {
    classes.push('disabled')
  }
  return classes.join(' ')
}

const TodoItem = ({ id, status, content, onToggle }) => (
  <li className={getTodoClass(status)} onClick={() => onToggle(id)}>
    {content}
  </li>
)

const TodosList = ({ items, onToggle }) => (
  <ul className={'list-group todos-list-group'}>
    {items.map(item => 
      <TodoItem
        {...item}
        key={item.id}
        onToggle={onToggle}
      />
    )}
  </ul>
)

const TodosListView = ({ todos, toggleTodo, clearDone }) => {
  if (!todos.length) {
    return null
  }
  return (
    <div>
      <hr />
      <TodosList items={todos} onToggle={toggleTodo} />
      <hr />
      <button
        className={'btn btn-link'}
        onClick={clearDone}
        children={'Clear stuff I have done'}
      />
    </div>
  )
}

// Stateful component that handle the form typing and local validation
class NewItemForm extends React.Component {
  constructor (props) {
    super(props)
    this.state = { text: '' }
  }
  onTextUpdate (text) {
    this.setState({ text })
  }
  onFormSubmit (e) {
    e.preventDefault()
    if (!this.state.text) {
      return
    }
    this.props.onNewValue(this.state.text)
    this.setState({ text: '' })
  }
  render () {
    return (
      <form onSubmit={(e) => this.onFormSubmit(e)}>
        <div className={'form-row'}>
          <div className={'col'}>
            <input
              type={'text'}
              className={'form-control'}
              placeholder={'remember the milk...'}
              value={this.state.text}
              onChange={e => this.onTextUpdate(e.target.value)}
            />
          </div>
          <div className={'col-xs-1'}>
            <button type={'submit'} className={'btn btn-primary'}>{'Add'}</button>
          </div>
        </div>
      </form>
    )
  }
}
NewItemForm.defaultProps = {
  onNewValue: () => {}
}

const getWelcomeTextClass = show => {
  const classes = [ 'welcome-text' ]
  if (show) {
    classes.push('welcome-text--visible')
  }
  return classes.join(' ')
}

const WelcomeText = ({ show }) => (
  <div className={getWelcomeTextClass(show)}>
    <p className={'lead'}>{'What are you going to do next?'}</p>
  </div>
)

// Many incoming props can be listed vertically
const AppComponent = ({
  name,
  todos,
  showWelcome,
  addTodo,
  toggleTodo,
  clearDone
}) => (
  <div className={'container'}>
    <h3>{name}</h3>
    <hr />
    <WelcomeText show={showWelcome} />
    <NewItemForm onNewValue={addTodo} />
    <TodosListView { ...({todos, toggleTodo, clearDone}) } />
  </div>
)


//
// -- Container
//

const mapStateToProps = ({ settings, todos }) => ({
  name: settings.name,
  showWelcome: todos.length === 0,
  todos,
})

const mapDispatchToProps = {
  addTodo, // services
  toggleTodo: todoId => ({ type: 'toggle-todo', todoId }),
  clearDone: () => ({ type: 'clear-done-todos' }),
}

const AppContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(AppComponent)



//
// -- Render the App
//

const renderApp = <AppContainer store={store} />
const renderTarget = document.getElementById('app')
ReactDOM.render(renderApp, renderTarget)
View Compiled

External CSS

  1. https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js
  3. https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.5.10/prop-types.js
  4. https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js
  5. https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js
  6. https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.2.0/redux-thunk.js