diff --git a/actions.js b/actions.js
index fab46c2..7ee5d03 100644
--- a/actions.js
+++ b/actions.js
@@ -6,6 +6,7 @@ export const ADD_TODO = 'ADD_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
export const CHANGE_THEME = 'CHANGE_THEME'
+export const UPDATE_SEARCH = 'UPDATE_SEARCH';
/*
* other constants
@@ -36,3 +37,10 @@ export function setVisibilityFilter(filter) {
export function changeTheme() {
return { type: CHANGE_THEME };
}
+
+export function updateSearch(searchTerm) {
+ return {
+ type: UPDATE_SEARCH,
+ searchTerm
+ };
+}
diff --git a/containers/App.js b/containers/App.js
index 599defd..ce71cd8 100644
--- a/containers/App.js
+++ b/containers/App.js
@@ -1,54 +1,32 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
-import { addTodo, completeTodo, setVisibilityFilter, changeTheme, VisibilityFilters } from '../actions'
+import { addTodo, completeTodo, setVisibilityFilter, changeTheme, VisibilityFilters, updateSearch } from '../actions'
import AddTodo from '../components/AddTodo'
import TodoList from '../components/TodoList'
import Footer from '../components/Footer'
import { memoize, createMemoizedFunction } from '../memoize'
-
-function selectTodos(todos, filter) {
- console.log("Recalculating selectTodos");
- switch (filter) {
- case VisibilityFilters.SHOW_ALL:
- return todos
- case VisibilityFilters.SHOW_COMPLETED:
- return todos.filter(todo => todo.completed)
- case VisibilityFilters.SHOW_ACTIVE:
- return todos.filter(todo => !todo.completed)
- }
-}
-
-function selectMatchingTodos(todos, search) {
- console.log("Recalculating matchingTodos");
- return todos.filter((todo) => { return todo.text.search(search) >= 0; });
-}
+import { createSelector, createStructuredSelector } from 'reselect';
class App extends Component {
- constructor(props, context) {
- super(props, context);
- this.state = { search: '' };
- }
-
- visibleTodos = createMemoizedFunction(() => [this.props.todos, this.props.visibilityFilter], selectTodos);
- matchingTodos = createMemoizedFunction(() => [this.visibleTodos(), this.state.search], selectMatchingTodos);
updateSearch = function(e) {
- this.setState({ search: e.target.value });
+ const { dispatch } = this.props;
+ dispatch(updateSearch(e.target.value));
}
render() {
console.log(this.props);
// Injected by connect() call:
- const { dispatch, visibilityFilter, currentTheme } = this.props
+ const { dispatch, matchingVisibleTodos, currentTheme, searchTerm, visibilityFilter } = this.props
return (
- Search:
+ Search:
dispatch(addTodo(text))
} />
dispatch(completeTodo(index))
} />
@@ -75,15 +53,44 @@ App.propTypes = {
]).isRequired
}
-// Which props do we want to inject, given the global state?
-// Note: use https://github.com/faassen/reselect for better performance.
-function select(state) {
- return {
- todos: state.todos,
- visibilityFilter: state.visibilityFilter,
- currentTheme: state.currentTheme
+function selectVisibleTodos(todos, filter) {
+ console.log("Recalculating selectTodos");
+ switch (filter) {
+ case VisibilityFilters.SHOW_ALL:
+ return todos
+ case VisibilityFilters.SHOW_COMPLETED:
+ return todos.filter(todo => todo.completed)
+ case VisibilityFilters.SHOW_ACTIVE:
+ return todos.filter(todo => !todo.completed)
}
}
+function selectMatchingTodos(todos, search) {
+ console.log("Recalculating matchingTodos");
+ return todos.filter((todo) => { return todo.text.search(search) >= 0; });
+}
+
+const todosSelector = state => state.todos;
+const visibilityFilterSelector = state => state.visibilityFilter;
+const currentThemeSelector = state => state.currentTheme;
+const searchTermSelector = state => state.searchTerm;
+
+const visibleTodosSelector = createSelector(
+ [todosSelector, visibilityFilterSelector],
+ selectVisibleTodos
+);
+
+const matchingVisibleTodosSelector = createSelector(
+ [visibleTodosSelector, searchTermSelector],
+ selectMatchingTodos
+);
+
+const select = createStructuredSelector({
+ matchingVisibleTodos: matchingVisibleTodosSelector,
+ visibilityFilter: visibilityFilterSelector,
+ currentTheme: currentThemeSelector,
+ searchTerm: searchTermSelector
+});
+
// Wrap the component to inject dispatch and state into it
export default connect(select)(App)
diff --git a/package.json b/package.json
index 5365197..64d9ed9 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
"react": "^0.14.0",
"react-dom": "^0.14.0",
"react-redux": "^4.0.0",
- "redux": "^3.0.0"
+ "redux": "^3.0.0",
+ "reselect": "^2.0.0"
},
"devDependencies": {
"babel-core": "^5.6.18",
diff --git a/reducers.js b/reducers.js
index 7c6a95b..bc73343 100644
--- a/reducers.js
+++ b/reducers.js
@@ -1,5 +1,5 @@
import { combineReducers } from 'redux'
-import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, CHANGE_THEME, VisibilityFilters } from './actions'
+import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, CHANGE_THEME, VisibilityFilters, UPDATE_SEARCH } from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
@@ -43,11 +43,21 @@ function currentTheme(state = 'theme-green', action) {
}
}
+function searchTerm(state = '', action) {
+ switch (action.type) {
+ case UPDATE_SEARCH:
+ return action.searchTerm;
+ default:
+ return state;
+ }
+}
+
const todoApp = combineReducers({
visibilityFilter,
todos,
- currentTheme
+ currentTheme,
+ searchTerm
})
export default todoApp