Skip to content

Commit 9b12801

Browse files
committed
Merge pull request #1 from gaearon/master
Optimize things a little bit
2 parents 51e049d + 4173886 commit 9b12801

File tree

20 files changed

+390
-310
lines changed

20 files changed

+390
-310
lines changed

.babelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"presets": ["es2015", "react"],
3+
"plugins": ["transform-object-rest-spread"],
34
"env": {
45
"development": {
56
"presets": ["react-hmre"]

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
dist/
1+
dist/*.js
22
npm-debug.log
33
node_modules/

actions/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as types from '../constants/ActionTypes'
22

3+
let nextId = 0;
34
export function addTodo(text) {
4-
return { type: types.ADD_TODO, text }
5+
return { type: types.ADD_TODO, text, id: (nextId++).toString() }
56
}
67

78
export function deleteTodo(id) {
@@ -23,3 +24,7 @@ export function completeAll() {
2324
export function clearCompleted() {
2425
return { type: types.CLEAR_COMPLETED }
2526
}
27+
28+
export function setFilter(filter) {
29+
return { type: types.SET_FILTER, filter }
30+
}

components/App.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React, { PropTypes } from 'react'
2+
import Header from '../components/Header'
3+
import MainSection from '../components/MainSection'
4+
import * as TodoActions from '../actions'
5+
6+
const App = () => (
7+
<div>
8+
<Header />
9+
<MainSection />
10+
</div>
11+
)
12+
13+
export default App

components/FilterLink.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react'
2+
import { connect } from 'react-redux'
3+
import classnames from 'classnames'
4+
import { setFilter } from '../actions'
5+
import { SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED } from '../constants/TodoFilters'
6+
7+
const FILTER_TITLES = {
8+
[SHOW_ALL]: 'All',
9+
[SHOW_ACTIVE]: 'Active',
10+
[SHOW_COMPLETED]: 'Completed'
11+
}
12+
13+
const FilterLink = ({ filter, selected, onClick }) => (
14+
<a className={classnames({ selected })}
15+
style={{ cursor: 'pointer' }}
16+
onClick={onClick}>
17+
{FILTER_TITLES[filter]}
18+
</a>
19+
)
20+
21+
const mapStateToProps = (state, ownProps) => ({
22+
selected: state.filter === ownProps.filter
23+
})
24+
25+
const mapDispatchToProps = (dispatch, ownProps) => ({
26+
onClick: () => dispatch(setFilter(ownProps.filter))
27+
})
28+
29+
export default connect(
30+
mapStateToProps,
31+
mapDispatchToProps
32+
)(FilterLink)

components/Footer.js

Lines changed: 54 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,60 @@
1-
import React, { PropTypes, Component } from 'react'
2-
import classnames from 'classnames'
1+
import React, { PropTypes } from 'react'
2+
import { connect } from 'react-redux'
3+
import FilterLink from './FilterLink'
4+
import { getCompletedCount, getListedCount } from '../reducers'
5+
import { clearCompleted } from '../actions'
36
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
47

5-
const FILTER_TITLES = {
6-
[SHOW_ALL]: 'All',
7-
[SHOW_ACTIVE]: 'Active',
8-
[SHOW_COMPLETED]: 'Completed'
9-
}
10-
11-
class Footer extends Component {
12-
renderTodoCount() {
13-
const { activeCount } = this.props
14-
const itemWord = activeCount === 1 ? 'item' : 'items'
15-
16-
return (
17-
<span className="todo-count">
18-
<strong>{activeCount || 'No'}</strong> {itemWord} left
19-
</span>
20-
)
21-
}
22-
23-
renderFilterLink(filter) {
24-
const title = FILTER_TITLES[filter]
25-
const { filter: selectedFilter, onShow } = this.props
26-
27-
return (
28-
<a className={classnames({ selected: filter === selectedFilter })}
29-
style={{ cursor: 'pointer' }}
30-
onClick={() => onShow(filter)}>
31-
{title}
32-
</a>
33-
)
34-
}
35-
36-
renderClearButton() {
37-
const { completedCount, onClearCompleted } = this.props
38-
if (completedCount > 0) {
39-
return (
40-
<button className="clear-completed"
41-
onClick={onClearCompleted} >
42-
Clear completed
43-
</button>
44-
)
45-
}
46-
}
47-
48-
render() {
49-
return (
50-
<footer className="footer">
51-
{this.renderTodoCount()}
52-
<ul className="filters">
53-
{[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter =>
54-
<li key={filter}>
55-
{this.renderFilterLink(filter)}
56-
</li>
57-
)}
58-
</ul>
59-
{this.renderClearButton()}
60-
</footer>
61-
)
62-
}
63-
}
64-
8+
const TodoCount = ({ activeCount }) => (
9+
<span className="todo-count">
10+
<strong>{activeCount || 'No'}</strong>
11+
{' '}
12+
{activeCount === 1 ? 'item' : 'items'} left
13+
</span>
14+
)
15+
16+
const ClearButton = ({ completedCount, clearCompleted }) => (
17+
<button className="clear-completed"
18+
onClick={clearCompleted} >
19+
Clear completed
20+
</button>
21+
)
22+
23+
const Footer = ({ filter, completedCount, listedCount, clearCompleted }) => (
24+
listedCount ? (
25+
<footer className="footer">
26+
<TodoCount activeCount={listedCount - completedCount} />
27+
<ul className="filters">
28+
{[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter =>
29+
<li key={filter}>
30+
<FilterLink filter={filter} />
31+
</li>
32+
)}
33+
</ul>
34+
{completedCount > 0 &&
35+
<ClearButton
36+
completedCount={completedCount}
37+
clearCompleted={clearCompleted}
38+
/>
39+
}
40+
</footer>
41+
) : (
42+
<span />
43+
)
44+
)
6545
Footer.propTypes = {
66-
completedCount: PropTypes.number.isRequired,
67-
activeCount: PropTypes.number.isRequired,
6846
filter: PropTypes.string.isRequired,
69-
onClearCompleted: PropTypes.func.isRequired,
70-
onShow: PropTypes.func.isRequired
47+
listedCount: PropTypes.number.isRequired,
48+
completedCount: PropTypes.number.isRequired,
7149
}
7250

73-
export default Footer
51+
const mapStateToProps = (state) => ({
52+
filter: state.filter,
53+
listedCount: getListedCount(state),
54+
completedCount: getCompletedCount(state),
55+
})
56+
57+
export default connect(
58+
mapStateToProps,
59+
{ clearCompleted }
60+
)(Footer)

components/Header.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
import React, { PropTypes, Component } from 'react'
1+
import React, { PropTypes } from 'react'
2+
import { connect } from 'react-redux'
23
import TodoTextInput from './TodoTextInput'
4+
import { addTodo } from '../actions'
35

4-
class Header extends Component {
5-
handleSave(text) {
6-
if (text.length !== 0) {
7-
this.props.addTodo(text)
8-
}
9-
}
10-
11-
render() {
12-
return (
13-
<header className="header">
14-
<h1>todos</h1>
15-
<TodoTextInput newTodo
16-
onSave={this.handleSave.bind(this)}
17-
placeholder="What needs to be done?" />
18-
</header>
19-
)
20-
}
21-
}
22-
6+
const Header = ({ addTodo }) => (
7+
<header className="header">
8+
<h1>todos</h1>
9+
<TodoTextInput
10+
newTodo
11+
placeholder="What needs to be done?"
12+
onSave={text => {
13+
if (text.length !== 0) {
14+
addTodo(text)
15+
}
16+
}}
17+
/>
18+
</header>
19+
)
2320
Header.propTypes = {
2421
addTodo: PropTypes.func.isRequired
2522
}
2623

27-
export default Header
24+
export default connect(
25+
null,
26+
{ addTodo }
27+
)(Header)

components/MainSection.js

Lines changed: 24 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,26 @@
1-
import React, { Component, PropTypes } from 'react'
1+
import React, { PropTypes } from 'react'
2+
import { connect } from 'react-redux'
23
import TodoItem from './TodoItem'
4+
import ToggleAll from './ToggleAll'
35
import Footer from './Footer'
4-
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
5-
6-
const TODO_FILTERS = {
7-
[SHOW_ALL]: () => true,
8-
[SHOW_ACTIVE]: todo => !todo.completed,
9-
[SHOW_COMPLETED]: todo => todo.completed
10-
}
11-
12-
class MainSection extends Component {
13-
constructor(props, context) {
14-
super(props, context)
15-
this.state = { filter: SHOW_ALL }
16-
}
17-
18-
handleClearCompleted() {
19-
this.props.actions.clearCompleted()
20-
}
21-
22-
handleShow(filter) {
23-
this.setState({ filter })
24-
}
25-
26-
renderToggleAll(completedCount) {
27-
const { todos, actions } = this.props
28-
if (todos.length > 0) {
29-
return (
30-
<input className="toggle-all"
31-
type="checkbox"
32-
checked={completedCount === todos.length}
33-
onChange={actions.completeAll} />
34-
)
35-
}
36-
}
37-
38-
renderFooter(completedCount) {
39-
const { todos } = this.props
40-
const { filter } = this.state
41-
const activeCount = todos.length - completedCount
42-
43-
if (todos.length) {
44-
return (
45-
<Footer completedCount={completedCount}
46-
activeCount={activeCount}
47-
filter={filter}
48-
onClearCompleted={this.handleClearCompleted.bind(this)}
49-
onShow={this.handleShow.bind(this)} />
50-
)
51-
}
52-
}
53-
54-
render() {
55-
const { todos, actions } = this.props
56-
const { filter } = this.state
57-
58-
const filteredTodos = todos.filter(TODO_FILTERS[filter])
59-
const completedCount = todos.reduce((count, todo) =>
60-
todo.completed ? count + 1 : count,
61-
0
62-
)
63-
64-
return (
65-
<section className="main">
66-
{this.renderToggleAll(completedCount)}
67-
<ul className="todo-list">
68-
{filteredTodos.map(todo =>
69-
<TodoItem key={todo.id} todo={todo} {...actions} />
70-
)}
71-
</ul>
72-
{this.renderFooter(completedCount)}
73-
</section>
74-
)
75-
}
76-
}
77-
78-
MainSection.propTypes = {
79-
todos: PropTypes.array.isRequired,
80-
actions: PropTypes.object.isRequired
81-
}
82-
83-
export default MainSection
6+
import { getVisibleTodoIds } from '../reducers'
7+
8+
const MainSection = ({ visibleIds }) => (
9+
<section className="main">
10+
<ToggleAll />
11+
<ul className="todo-list">
12+
{visibleIds.map(id =>
13+
<TodoItem key={id} id={id} />
14+
)}
15+
</ul>
16+
<Footer />
17+
</section>
18+
)
19+
20+
const mapStateToProps = (state) => ({
21+
visibleIds: getVisibleTodoIds(state)
22+
})
23+
24+
export default connect(
25+
mapStateToProps
26+
)(MainSection)

0 commit comments

Comments
 (0)