Code to be refactored:
let nextTodoId = 0; class TodoApp extends Component { render() { const { todos, visibilityFilter } = this.props; const visibleTodos = getVisibleTodos( todos, visibilityFilter ); return ( <div> <input ref={node => { this.input = node; }} /> <button onClick={() => { store.dispatch({ type: 'ADD_TODO', text: this.input.value, id: nextTodoId++ }); this.input.value = ''; }}> Add Todo </button> <ul> {visibleTodos.map(todo => <li key={todo.id} onClick={() => { store.dispatch({ type: 'TOGGLE_TODO', id: todo.id }); }} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </li> )} </ul> <p> Show: {' '} <FilterLink filter='SHOW_ALL' currentFilter={visibilityFilter} > All </FilterLink> {', '} <FilterLink filter='SHOW_ACTIVE' currentFilter={visibilityFilter} > Active </FilterLink> {', '} <FilterLink filter='SHOW_COMPLETED' currentFilter={visibilityFilter} > Completed </FilterLink> </p> </div> ); } }
const FilterLink = ({ filter, currentFilter, children }) => { if (filter === currentFilter) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter }); }} > {children} </a> ); };
Refactor footer part into a functional component, which contains all these three filter links. Pass in visibilityFilter as props:
const Footer = ({ visibilityFilter }) => ( <p> Show: {' '} <FilterLink filter='SHOW_ALL' currentFilter={visibilityFilter} > All </FilterLink> {', '} <FilterLink filter='SHOW_ACTIVE' currentFilter={visibilityFilter} > Active </FilterLink> {', '} <FilterLink filter='SHOW_COMPLETED' currentFilter={visibilityFilter} > Completed </FilterLink> </p> );
In the FilterLink, we want it to be presentational components. However, the filter link includes a short dispatch call. I am replacing it with an on click call. I pass the filter as the single parameter for the calling component's convenience. I add on click to the props.
const FilterLink = ({ filter, currentFilter, children, onFilterClick }) => { if (filter === currentFilter) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); onFilterClick(filter); }} > {children} </a> ); };
const Footer = ({ visibilityFilter, onFilterClick }) => ( <p> Show: {' '} <FilterLink filter='SHOW_ALL' currentFilter={visibilityFilter} onFilterClick={onFilterClick} > All </FilterLink> {', '} <FilterLink filter='SHOW_ACTIVE' currentFilter={visibilityFilter} onFilterClick={onFilterClick} > Active </FilterLink> {', '} <FilterLink filter='SHOW_COMPLETED' currentFilter={visibilityFilter} onFilterClick={onFilterClick} > Completed </FilterLink> </p> );
-----------------------------------
Code:
const todo = (state, action) => { switch (action.type) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false }; case 'TOGGLE_TODO': if (state.id !== action.id) { return state; } return { ...state, completed: !state.completed }; default: return state; } }; const todos = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, todo(undefined, action) ]; case 'TOGGLE_TODO': return state.map(t => todo(t, action) ); default: return state; } }; const visibilityFilter = ( state = 'SHOW_ALL', action ) => { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter; default: return state; } }; const { combineReducers } = Redux; const todoApp = combineReducers({ todos, visibilityFilter }); const { createStore } = Redux; const store = createStore(todoApp); const { Component } = React; /** Functional compoment, persental compoment: doesn't need to know what to do, just show the interface, call the callback function. */ const AddTodo = ({ onAddTodo }) => { let input; return ( <div> <input ref={node => { input = node; }} /> <button onClick={() => { onAddTodo(input.value); input.value = ''; }}> Add Todo </button> </div> ); } /* Functional component */ const Footer = ({ visibilityFilter, onFilterClick }) => ( <p> Show: {' '} <FilterLink filter='SHOW_ALL' currentFilter={visibilityFilter} onFilterClick={onFilterClick} > All </FilterLink> {', '} <FilterLink filter='SHOW_ACTIVE' currentFilter={visibilityFilter} onFilterClick={onFilterClick} > Active </FilterLink> {', '} <FilterLink filter='SHOW_COMPLETED' currentFilter={visibilityFilter} onFilterClick={onFilterClick} > Completed </FilterLink> </p> ); const FilterLink = ({ filter, currentFilter, children, onFilterClick }) => { if (filter === currentFilter) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); onFilterClick(filter); }} > {children} </a> ); }; const getVisibleTodos = ( todos, filter ) => { switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter( t => t.completed ); case 'SHOW_ACTIVE': return todos.filter( t => !t.completed ); } } let nextTodoId = 0; class TodoApp extends Component { render() { const { todos, visibilityFilter } = this.props; const visibleTodos = getVisibleTodos( todos, visibilityFilter ); return ( <div> <AddTodo onAddTodo={ text => store.dispatch({ type: 'ADD_TODO', id: nextTodoId++, text }) } /> <ul> {visibleTodos.map(todo => <li key={todo.id} onClick={() => { store.dispatch({ type: 'TOGGLE_TODO', id: todo.id }); }} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </li> )} </ul> <Footer visibilityFilter = {visibilityFilter} onFilterClick={ (filter) => { store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter }); }} /> </div> ); } } const render = () => { ReactDOM.render( <TodoApp {...store.getState()} />, document.getElementById('root') ); }; store.subscribe(render); render();