Skip to content

Commit

Permalink
Memoize prop transformation functions and specify their arguments
Browse files Browse the repository at this point in the history
Meant to be similar to reselect's `createSelector`, but instead of
reaching into redux `state`, it reaches into the component's `this`.
  • Loading branch information
gladtocode committed Nov 6, 2015
1 parent 13685d6 commit 7015a57
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 16 deletions.
2 changes: 1 addition & 1 deletion components/AddTodo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default class AddTodo extends Component {
render() {
return (
<div>
<input type='text' ref='input' />
Add: <input type='text' ref='input' />
<button onClick={(e) => this.handleClick(e)}>
Add
</button>
Expand Down
50 changes: 35 additions & 15 deletions containers/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,51 @@ import { addTodo, completeTodo, setVisibilityFilter, changeTheme, VisibilityFilt
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; });
}

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 });
}

render() {
console.log(this.props);
// Injected by connect() call:
const { dispatch, visibleTodos, visibilityFilter, currentTheme } = this.props
const { dispatch, visibilityFilter, currentTheme } = this.props
return (
<div className={currentTheme}>
Search: <input type="text" onChange={this.updateSearch.bind(this)}/><br/>
<AddTodo
onAddClick={text =>
dispatch(addTodo(text))
} />
<TodoList
todos={visibleTodos}
todos={this.matchingTodos()}
onTodoClick={index =>
dispatch(completeTodo(index))
} />
Expand All @@ -33,7 +64,7 @@ class App extends Component {
}

App.propTypes = {
visibleTodos: PropTypes.arrayOf(PropTypes.shape({
todos: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired
})),
Expand All @@ -44,22 +75,11 @@ App.propTypes = {
]).isRequired
}

function selectTodos(todos, filter) {
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)
}
}

// 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 {
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
todos: state.todos,
visibilityFilter: state.visibilityFilter,
currentTheme: state.currentTheme
}
Expand Down
29 changes: 29 additions & 0 deletions memoize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// This memoizes a function by remembering its last args and its last result
// and returning the last result if the args are the same as the last call.
export function memoize(func) {
var lastArgs = null;
var lastResult;

function argsDifferent(args) {
return lastArgs === null ||
lastArgs.length != args.length ||
args.some((arg, idx) => { return arg !== lastArgs[idx] });
}

return function(...args) {
if(argsDifferent(args)) {
lastArgs = args;
lastResult = func(...args);
}
return lastResult
}
}

// This memoizes `func` and returns a function that calls
// the memoized `func` with the arguments returned by `argFunc`
export function createMemoizedFunction(argFunc, func) {
var memoized = memoize(func);
return function() {
return memoized(...argFunc());
}
}

0 comments on commit 7015a57

Please sign in to comment.