Skip to content

Commit 7015a57

Browse files
committed
Memoize prop transformation functions and specify their arguments
Meant to be similar to reselect's `createSelector`, but instead of reaching into redux `state`, it reaches into the component's `this`.
1 parent 13685d6 commit 7015a57

File tree

3 files changed

+65
-16
lines changed

3 files changed

+65
-16
lines changed

components/AddTodo.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default class AddTodo extends Component {
44
render() {
55
return (
66
<div>
7-
<input type='text' ref='input' />
7+
Add: <input type='text' ref='input' />
88
<button onClick={(e) => this.handleClick(e)}>
99
Add
1010
</button>

containers/App.js

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,51 @@ import { addTodo, completeTodo, setVisibilityFilter, changeTheme, VisibilityFilt
44
import AddTodo from '../components/AddTodo'
55
import TodoList from '../components/TodoList'
66
import Footer from '../components/Footer'
7+
import { memoize, createMemoizedFunction } from '../memoize'
8+
9+
function selectTodos(todos, filter) {
10+
console.log("Recalculating selectTodos");
11+
switch (filter) {
12+
case VisibilityFilters.SHOW_ALL:
13+
return todos
14+
case VisibilityFilters.SHOW_COMPLETED:
15+
return todos.filter(todo => todo.completed)
16+
case VisibilityFilters.SHOW_ACTIVE:
17+
return todos.filter(todo => !todo.completed)
18+
}
19+
}
20+
21+
function selectMatchingTodos(todos, search) {
22+
console.log("Recalculating matchingTodos");
23+
return todos.filter((todo) => { return todo.text.search(search) >= 0; });
24+
}
725

826
class App extends Component {
27+
constructor(props, context) {
28+
super(props, context);
29+
this.state = { search: '' };
30+
}
31+
32+
visibleTodos = createMemoizedFunction(() => [this.props.todos, this.props.visibilityFilter], selectTodos);
33+
matchingTodos = createMemoizedFunction(() => [this.visibleTodos(), this.state.search], selectMatchingTodos);
34+
35+
updateSearch = function(e) {
36+
this.setState({ search: e.target.value });
37+
}
38+
939
render() {
1040
console.log(this.props);
1141
// Injected by connect() call:
12-
const { dispatch, visibleTodos, visibilityFilter, currentTheme } = this.props
42+
const { dispatch, visibilityFilter, currentTheme } = this.props
1343
return (
1444
<div className={currentTheme}>
45+
Search: <input type="text" onChange={this.updateSearch.bind(this)}/><br/>
1546
<AddTodo
1647
onAddClick={text =>
1748
dispatch(addTodo(text))
1849
} />
1950
<TodoList
20-
todos={visibleTodos}
51+
todos={this.matchingTodos()}
2152
onTodoClick={index =>
2253
dispatch(completeTodo(index))
2354
} />
@@ -33,7 +64,7 @@ class App extends Component {
3364
}
3465

3566
App.propTypes = {
36-
visibleTodos: PropTypes.arrayOf(PropTypes.shape({
67+
todos: PropTypes.arrayOf(PropTypes.shape({
3768
text: PropTypes.string.isRequired,
3869
completed: PropTypes.bool.isRequired
3970
})),
@@ -44,22 +75,11 @@ App.propTypes = {
4475
]).isRequired
4576
}
4677

47-
function selectTodos(todos, filter) {
48-
switch (filter) {
49-
case VisibilityFilters.SHOW_ALL:
50-
return todos
51-
case VisibilityFilters.SHOW_COMPLETED:
52-
return todos.filter(todo => todo.completed)
53-
case VisibilityFilters.SHOW_ACTIVE:
54-
return todos.filter(todo => !todo.completed)
55-
}
56-
}
57-
5878
// Which props do we want to inject, given the global state?
5979
// Note: use https://github.com/faassen/reselect for better performance.
6080
function select(state) {
6181
return {
62-
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
82+
todos: state.todos,
6383
visibilityFilter: state.visibilityFilter,
6484
currentTheme: state.currentTheme
6585
}

memoize.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This memoizes a function by remembering its last args and its last result
2+
// and returning the last result if the args are the same as the last call.
3+
export function memoize(func) {
4+
var lastArgs = null;
5+
var lastResult;
6+
7+
function argsDifferent(args) {
8+
return lastArgs === null ||
9+
lastArgs.length != args.length ||
10+
args.some((arg, idx) => { return arg !== lastArgs[idx] });
11+
}
12+
13+
return function(...args) {
14+
if(argsDifferent(args)) {
15+
lastArgs = args;
16+
lastResult = func(...args);
17+
}
18+
return lastResult
19+
}
20+
}
21+
22+
// This memoizes `func` and returns a function that calls
23+
// the memoized `func` with the arguments returned by `argFunc`
24+
export function createMemoizedFunction(argFunc, func) {
25+
var memoized = memoize(func);
26+
return function() {
27+
return memoized(...argFunc());
28+
}
29+
}

0 commit comments

Comments
 (0)