diff --git a/.babelrc b/.babelrc index d0962f5..c91e0a0 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,6 @@ { "presets": ["es2015", "react"], + "plugins": ["transform-object-rest-spread"], "env": { "development": { "presets": ["react-hmre"] diff --git a/.gitignore b/.gitignore index 6df1c4e..b0b8746 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -dist/ +dist/*.js npm-debug.log node_modules/ diff --git a/actions/index.js b/actions/index.js index 2e52a7c..42508aa 100644 --- a/actions/index.js +++ b/actions/index.js @@ -1,7 +1,8 @@ import * as types from '../constants/ActionTypes' +let nextId = 0; export function addTodo(text) { - return { type: types.ADD_TODO, text } + return { type: types.ADD_TODO, text, id: (nextId++).toString() } } export function deleteTodo(id) { @@ -23,3 +24,7 @@ export function completeAll() { export function clearCompleted() { return { type: types.CLEAR_COMPLETED } } + +export function setFilter(filter) { + return { type: types.SET_FILTER, filter } +} diff --git a/components/App.js b/components/App.js new file mode 100644 index 0000000..5a141d6 --- /dev/null +++ b/components/App.js @@ -0,0 +1,13 @@ +import React, { PropTypes } from 'react' +import Header from '../components/Header' +import MainSection from '../components/MainSection' +import * as TodoActions from '../actions' + +const App = () => ( +
+
+ +
+) + +export default App diff --git a/components/FilterLink.js b/components/FilterLink.js new file mode 100644 index 0000000..dd2ad1b --- /dev/null +++ b/components/FilterLink.js @@ -0,0 +1,32 @@ +import React from 'react' +import { connect } from 'react-redux' +import classnames from 'classnames' +import { setFilter } from '../actions' +import { SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED } from '../constants/TodoFilters' + +const FILTER_TITLES = { + [SHOW_ALL]: 'All', + [SHOW_ACTIVE]: 'Active', + [SHOW_COMPLETED]: 'Completed' +} + +const FilterLink = ({ filter, selected, onClick }) => ( + + {FILTER_TITLES[filter]} + +) + +const mapStateToProps = (state, ownProps) => ({ + selected: state.filter === ownProps.filter +}) + +const mapDispatchToProps = (dispatch, ownProps) => ({ + onClick: () => dispatch(setFilter(ownProps.filter)) +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(FilterLink) diff --git a/components/Footer.js b/components/Footer.js index 8ae9c59..f8063b2 100644 --- a/components/Footer.js +++ b/components/Footer.js @@ -1,73 +1,60 @@ -import React, { PropTypes, Component } from 'react' -import classnames from 'classnames' +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import FilterLink from './FilterLink' +import { getCompletedCount, getListedCount } from '../reducers' +import { clearCompleted } from '../actions' import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' -const FILTER_TITLES = { - [SHOW_ALL]: 'All', - [SHOW_ACTIVE]: 'Active', - [SHOW_COMPLETED]: 'Completed' -} - -class Footer extends Component { - renderTodoCount() { - const { activeCount } = this.props - const itemWord = activeCount === 1 ? 'item' : 'items' - - return ( - - {activeCount || 'No'} {itemWord} left - - ) - } - - renderFilterLink(filter) { - const title = FILTER_TITLES[filter] - const { filter: selectedFilter, onShow } = this.props - - return ( - onShow(filter)}> - {title} - - ) - } - - renderClearButton() { - const { completedCount, onClearCompleted } = this.props - if (completedCount > 0) { - return ( - - ) - } - } - - render() { - return ( - - ) - } -} - +const TodoCount = ({ activeCount }) => ( + + {activeCount || 'No'} + {' '} + {activeCount === 1 ? 'item' : 'items'} left + +) + +const ClearButton = ({ completedCount, clearCompleted }) => ( + +) + +const Footer = ({ filter, completedCount, listedCount, clearCompleted }) => ( + listedCount ? ( + + ) : ( + + ) +) Footer.propTypes = { - completedCount: PropTypes.number.isRequired, - activeCount: PropTypes.number.isRequired, filter: PropTypes.string.isRequired, - onClearCompleted: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired + listedCount: PropTypes.number.isRequired, + completedCount: PropTypes.number.isRequired, } -export default Footer +const mapStateToProps = (state) => ({ + filter: state.filter, + listedCount: getListedCount(state), + completedCount: getCompletedCount(state), +}) + +export default connect( + mapStateToProps, + { clearCompleted } +)(Footer) diff --git a/components/Header.js b/components/Header.js index aeb8303..2dfa552 100644 --- a/components/Header.js +++ b/components/Header.js @@ -1,27 +1,27 @@ -import React, { PropTypes, Component } from 'react' +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' import TodoTextInput from './TodoTextInput' +import { addTodo } from '../actions' -class Header extends Component { - handleSave(text) { - if (text.length !== 0) { - this.props.addTodo(text) - } - } - - render() { - return ( -
-

todos

- -
- ) - } -} - +const Header = ({ addTodo }) => ( +
+

todos

+ { + if (text.length !== 0) { + addTodo(text) + } + }} + /> +
+) Header.propTypes = { addTodo: PropTypes.func.isRequired } -export default Header +export default connect( + null, + { addTodo } +)(Header) diff --git a/components/MainSection.js b/components/MainSection.js index f5c2804..468fb57 100644 --- a/components/MainSection.js +++ b/components/MainSection.js @@ -1,83 +1,26 @@ -import React, { Component, PropTypes } from 'react' +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' import TodoItem from './TodoItem' +import ToggleAll from './ToggleAll' import Footer from './Footer' -import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' - -const TODO_FILTERS = { - [SHOW_ALL]: () => true, - [SHOW_ACTIVE]: todo => !todo.completed, - [SHOW_COMPLETED]: todo => todo.completed -} - -class MainSection extends Component { - constructor(props, context) { - super(props, context) - this.state = { filter: SHOW_ALL } - } - - handleClearCompleted() { - this.props.actions.clearCompleted() - } - - handleShow(filter) { - this.setState({ filter }) - } - - renderToggleAll(completedCount) { - const { todos, actions } = this.props - if (todos.length > 0) { - return ( - - ) - } - } - - renderFooter(completedCount) { - const { todos } = this.props - const { filter } = this.state - const activeCount = todos.length - completedCount - - if (todos.length) { - return ( -